Written by Aunoy Poddar July 21st, 2022

Process the puncta quantified raw data

current_file <- rstudioapi::getActiveDocumentContext()$path
output_file <- stringr::str_replace(current_file, '.Rmd', '.R')
knitr::purl(current_file, output = output_file)
file.edit(output_file)

Import packages and functions

library(Seurat)
library(tictoc)
library(ggplot2)
library(patchwork)
library(pheatmap)
library(RColorBrewer)
library(tidyverse)
library(gridExtra)
library(png)
library(cowplot)
library(magick)
library(scales)
library(packcircles)
library(ggalt)

Load the data

data_dir = '/home/aunoy/st/arc_profiling/st_analysis/hand_annotated_data/rethresholded'
clump_dir = '/home/aunoy/st/arc_profiling/st_analysis/hand_annotated_data/clumps'
meta_dir = '/home/aunoy/st/arc_profiling/st_analysis/hand_annotated_data/overlay'
output_dir_plot = '/home/aunoy/st/arc_profiling/st_analysis/results/plots'
output_dir_tbls = '/home/aunoy/st/arc_profiling/st_analysis/results/tables'

plot the meta data real quick

meta_ntrscts = read.csv(file.path(clump_dir, 'meta', 'META_ntrsct.csv'), header = FALSE) %>%
  as_tibble()

Merge both datasets and generate a metadata column that corresponds

to the cell

df_408 = data.frame()
for (file_name in list.files(data_dir)){
  if(grepl('164', file_name)){
    next
  }
  #if(grepl('408_TC', file_name) | grepl('408_vMS', file_name)){
  #  next
  #}
  print(file_name)

  df_to_append <- read.table(file.path(data_dir, file_name), sep = ',', header = TRUE)
  
  print(df_to_append)

  while(length(ind <- which(df_to_append$Image.Name == "")) > 0){
    df_to_append$Image.Name[ind] <- df_to_append$Image.Name[ind -1]
  }
  
  colnames(df_to_append) <- toupper(colnames(df_to_append))
  df_to_append <- df_to_append %>%
    mutate(area = strsplit(file_name, '.csv')[[1]])
  
  ## Add relative_XY_position
  
  if(!is_empty(df_408)){
    df_to_append <- df_to_append %>%
          dplyr::select(colnames(df_408))
  }
  df_408 <- rbind(df_408, df_to_append)
}
[1] "408_CC.csv"
[1] "408_dMS_TC.csv"
[1] "408_MS_CC.csv"
[1] "408_TC.csv"
[1] "408_vMS_TC.csv"
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='_Cluster', replacement=''))
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='[*]', replacement=''))
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='X', replacement=''))
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='L2_', replacement='L2-'))
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='-L2', replacement='_L2'))
df_408$IMAGE.NAME = unlist(lapply(df_408$IMAGE.NAME, gsub, pattern='Tc_12', replacement='TC_12'))
## Missing
df_408 = df_408[df_408$IMAGE.NAME != 'Layer1', ]
df_408 = df_408[df_408$IMAGE.NAME != 'TC_1', ]
df_408 = df_408[df_408$IMAGE.NAME != 'TC_18', ]
df_408 = df_408[df_408$IMAGE.NAME != 'TC_19', ]
#df_408$IMAGE.NAME = toupper(df_408$IMAGE.NAME)
unique(df_408$IMAGE.NAME)
 [1] "CC_Cortical1" "CC_Cortical2" "CC_L2-1"      "CC_L2-2"      "CC_L2-3"      "TC_2"         "TC_3"        
 [8] "TC_4"         "TC_5"         "TC_6"         "TC_7"         "TC_8"         "TC_9"         "TC_10"       
[15] "CC_4"         "CC_5"         "CC_6"         "CC_7"         "CC_8"         "CC_9"         "CC_10"       
[22] "CC_11"        "CC_12"        "TC_16"        "TC_17"        "TC_20"        "TC_11"        "TC_12"       
[29] "TC_13"        "TC_14"        "TC_15"       

Order the images

unique(df_408$IMAGE.NAME)
 [1] "CC_Cortical1" "CC_Cortical2" "CC_L2-1"      "CC_L2-2"      "CC_L2-3"      "TC_2"         "TC_3"        
 [8] "TC_4"         "TC_5"         "TC_6"         "TC_7"         "TC_8"         "TC_9"         "TC_10"       
[15] "CC_4"         "CC_5"         "CC_6"         "CC_7"         "CC_8"         "CC_9"         "CC_10"       
[22] "CC_11"        "CC_12"        "TC_16"        "TC_17"        "TC_20"        "TC_11"        "TC_12"       
[29] "TC_13"        "TC_14"        "TC_15"       
images_ordered = c('TC_20', 'TC_17', 'TC_16', 'TC_15', 'TC_14', 'TC_13', 'TC_12', 'TC_11', 'TC_10', 'TC_9', 'TC_8', 'TC_7', 'TC_6', 'TC_5',
                   'TC_4', 'TC_3', 'TC_2', 'CC_4', 'CC_5', 'CC_6', 'CC_7', 'CC_8', 'CC_9', 'CC_10', 'CC_11', 'CC_12', 'CC_L2-3', 'CC_L2-2', 'CC_L2-1', 'CC_Cortical1', 'CC_Cortical2')
x_horz = 1:length(images_ordered) * 35
y_horz = rep(0, length(images_ordered))
horz_embedding = data.frame()
df_408$X_horz = -1
df_408$Y_horz = -1
df_408$clump = NaN
clump_header = '408_'
clump_files = list.files(clump_dir)
IMAGE_SIZE = 1024
## This is the size of an image in the global coordinate space
IMAGE_LEN = 25

images = list.files(meta_dir)
for(i in 1:length(images_ordered)){
    image_name = images_ordered[i]
    print(image_name)
    split_names = strsplit(image_name, '_')
    cortex = toupper(split_names[[1]][1])
    number = split_names[[1]][2]
    number_csv = paste0('_', number, '.csv')
    filename = images[grepl(cortex, images) & grepl(number_csv, images) & grepl('408', images)]
    coordinates = read.table(file.path(meta_dir, filename), sep = ',', header = TRUE)
    ## checked already that lists are equal, missing 1, 18, 19 for now, layer 1 and others
    
    ## Get the clumps
    filename = clump_files[grepl(clump_header, clump_files) & grepl(paste0(image_name, '_'),clump_files)]
    clump_df = as.data.frame(t(read.csv(file.path(clump_dir, filename), header = FALSE)))
    colnames(clump_df) = c('roi', 'cluster')
    clump_df$roi = as.numeric(clump_df$roi)
    
    image_idxs = which(df_408$IMAGE.NAME == image_name)
    clump_df$roi_idxs = image_idxs[1] + clump_df$roi - 1
    df_408[clump_df$roi_idxs, "clump"] = paste0(clump_header, image_name, '_', clump_df$cluster)
    
    ## so this is a little tricky, so need to get it right
    ## Remember, it is the top right that the coordinate is coming from, but
    ## the bottom right is the new coordinate space.
    ## so first when we get the original coordinate space, to set to relative
    ## of bottom would be the same X, but 1024 - Y
    
    ## push out the coordinates for better visualization
    #x_repelled <- (512 - coordinates$X_Coordinate_In_pixels)
    
    
    df_408[df_408$IMAGE.NAME == image_name, 'X_horz'] = (coordinates$X_Coordinate_In_pixels / 
                                                      IMAGE_SIZE * IMAGE_LEN) + y_horz[i]
    df_408[df_408$IMAGE.NAME == image_name, 'Y_horz'] = ((1024-coordinates$Y_Coordinate_In_pixels) / 
                                                      IMAGE_SIZE * IMAGE_LEN) + x_horz[i]
}
[1] "TC_20"
[1] "TC_17"
[1] "TC_16"
[1] "TC_15"
[1] "TC_14"
[1] "TC_13"
[1] "TC_12"
[1] "TC_11"
[1] "TC_10"
[1] "TC_9"
[1] "TC_8"
[1] "TC_7"
[1] "TC_6"
[1] "TC_5"
[1] "TC_4"
[1] "TC_3"
[1] "TC_2"
[1] "CC_4"
[1] "CC_5"
[1] "CC_6"
[1] "CC_7"
[1] "CC_8"
[1] "CC_9"
[1] "CC_10"
[1] "CC_11"
[1] "CC_12"
[1] "CC_L2-3"
[1] "CC_L2-2"
[1] "CC_L2-1"
[1] "CC_Cortical1"
[1] "CC_Cortical2"

We have the coordinates for 408_TC and others

rownames(df_408) = 1:nrow(df_408)
jy_408 = df_408 %>%
  dplyr::select(-c(area, IMAGE.NAME, X_horz, Y_horz, clump)) %>%
  t() %>%
  CreateSeuratObject()

just set everything from below 1 in ratio to zero

jy_408 <- NormalizeData(jy_408, scale.factor = 1e5) ###
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
normed = GetAssayData(jy_408, slot = 'data')
normed[normed < 3] = 0
for(gene_name in rownames(jy_408)) {
  mdn_gene_expr = median(normed[gene_name, normed[gene_name, ] > 0])
  #print(gene_name)
  #print(mdn_gene_expr)
  #normed[gene_name, normed[gene_name, ] < mdn_gene_expr] = 0
}
jy_408 <- SetAssayData(jy_408, slot = 'data', normed)
jy_408 <- FindVariableFeatures(jy_408, selection.method = "vst")
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
all.genes <- rownames(jy_408)
jy_408 <- ScaleData(jy_408, features = all.genes)
Centering and scaling data matrix

  |                                                                                                                
  |                                                                                                          |   0%
  |                                                                                                                
  |==========================================================================================================| 100%
jy_408 <- RunPCA(jy_408, approx = FALSE)
Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.PC_ 1 
Positive:  VIP, ASCL1, CXCR4, RELN, SATB2, MAF1, KIA0319, EMX1, GAD1, CXCL12 
       PAX6, DLX2, SST, PROX1, LHX6, NCAM1 
Negative:  DCX, TBR1, LRP8, SCGN, COUPTF2, EGFR, CXCL14, TSHZ1, GSX2, EOMES 
       SP8, NKX2.1, CALB2, CXCR7, DCDC2, VLDLR 
PC_ 2 
Positive:  TBR1, EOMES, KIA0319, DCDC2, LRP8, CALB2, DCX, SATB2, CXCL12, EGFR 
       EMX1, ASCL1, COUPTF2, PAX6, RELN, CXCR4 
Negative:  MAF1, TSHZ1, NKX2.1, SST, GAD1, DLX2, PROX1, SP8, GSX2, VIP 
       SCGN, LHX6, VLDLR, CXCL14, CXCR7, NCAM1 
PC_ 3 
Positive:  COUPTF2, DLX2, PROX1, GAD1, CXCR4, LHX6, LRP8, TSHZ1, CXCL14, EOMES 
       NKX2.1, SP8, CALB2, CXCL12, DCDC2, VLDLR 
Negative:  SATB2, RELN, MAF1, PAX6, ASCL1, SCGN, SST, EMX1, GSX2, NCAM1 
       DCX, KIA0319, VIP, EGFR, CXCR7, TBR1 
PC_ 4 
Positive:  NCAM1, TSHZ1, VLDLR, CXCL14, PROX1, EGFR, CXCR7, DCX, ASCL1, DCDC2 
       CALB2, PAX6, GSX2, SCGN, EOMES, KIA0319 
Negative:  LHX6, DLX2, LRP8, SP8, VIP, SST, COUPTF2, NKX2.1, TBR1, CXCR4 
       EMX1, RELN, CXCL12, MAF1, GAD1, SATB2 
PC_ 5 
Positive:  GSX2, SP8, NKX2.1, SCGN, COUPTF2, CXCL14, EGFR, EMX1, TSHZ1, CALB2 
       CXCL12, TBR1, PAX6, SATB2, MAF1, KIA0319 
Negative:  SST, CXCR4, DCDC2, GAD1, PROX1, EOMES, DLX2, VIP, DCX, VLDLR 
       RELN, NCAM1, LHX6, ASCL1, CXCR7, LRP8 
jy_408 <- FindNeighbors(jy_408, dims = 1:30)
Computing nearest neighbor graph
Computing SNN
jy_408 <- FindClusters(jy_408, resolution = 1.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 1013
Number of edges: 35954

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.5629
Number of communities: 12
Elapsed time: 0 seconds
jy_408 <- RunUMAP(jy_408, dims = 1:30)
15:46:39 UMAP embedding parameters a = 0.9922 b = 1.112
15:46:39 Read 1013 rows and found 30 numeric columns
15:46:39 Using Annoy for neighbor search, n_neighbors = 30
15:46:39 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:39 Writing NN index file to temp file /tmp/RtmpaBJ93n/file83475b9cef8d
15:46:39 Searching Annoy index using 1 thread, search_k = 3000
15:46:40 Annoy recall = 100%
15:46:40 Commencing smooth kNN distance calibration using 1 thread
15:46:41 Initializing from normalized Laplacian + noise
15:46:41 Commencing optimization for 500 epochs, with 38030 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:42 Optimization finished
DimPlot(jy_408,  reduction = "umap", group.by = 'seurat_clusters') + NoAxes()

jy_408$clump = df_408$clump

DimPlot(jy_408,  reduction = "umap", group.by = 'clump') + NoAxes() + NoLegend()

hcoords = df_408 %>% dplyr::select(c('X_horz', 'Y_horz')) %>% as.matrix()
colnames(hcoords) <- c('pixel_1', 'pixel_2')

jy_408[["H"]] <- CreateDimReducObject(embeddings = hcoords, key = "pixel_", assay = DefaultAssay(jy_408))

Merge both datasets and generate a metadata column that corresponds

to the cell

df_164 = data.frame()
for (file_name in list.files(data_dir)){
  print(file_name)
  if(grepl('408', file_name)){
    next
  }
  #if(grepl('408_TC', file_name) | grepl('408_vMS', file_name)){
  #  next
  #}
  df_to_append <- read.table(file.path(data_dir, file_name), sep = ',', header = TRUE)
  while(length(ind <- which(df_to_append$Image.Name == "")) > 0){
    df_to_append$Image.Name[ind] <- df_to_append$Image.Name[ind -1]
  }
  
  colnames(df_to_append) <- toupper(colnames(df_to_append))
  df_to_append <- df_to_append %>%
    mutate(area = strsplit(file_name, '.csv')[[1]])
  
  ## Add relative_XY_position
  
  if(!is_empty(df_164)){
    df_to_append <- df_to_append %>%
          dplyr::select(colnames(df_164))
  }
  df_164 <- rbind(df_164, df_to_append)
}
[1] "164_CC.csv"
[1] "164_MS_CC.csv"
[1] "164_MS_TC.csv"
[1] "164_TC.csv"
[1] "408_CC.csv"
[1] "408_dMS_TC.csv"
[1] "408_MS_CC.csv"
[1] "408_TC.csv"
[1] "408_vMS_TC.csv"
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='CC-', replacement='CC_'))
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='[*]', replacement=''))
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='X', replacement=''))
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='L2', replacement='CC_L2'))
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='L2_', replacement='L2-'))
df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='-L2', replacement='_L2'))
tc_cortical_names_bad = df_164[grepl('TC', df_164$area) & grepl('Cortical', df_164$IMAGE.NAME), 'IMAGE.NAME']
df_164[grepl('TC', df_164$area) & grepl('Cortical', df_164$IMAGE.NAME), 'IMAGE.NAME'] = unlist(lapply(tc_cortical_names_bad, gsub, pattern='Cort', replacement='TC_Cort'))
cc_cortical_names_bad = df_164[grepl('CC', df_164$area) & grepl('Cortical', df_164$IMAGE.NAME), 'IMAGE.NAME']
df_164[grepl('CC', df_164$area) & grepl('Cortical', df_164$IMAGE.NAME), 'IMAGE.NAME'] = unlist(lapply(cc_cortical_names_bad, gsub, pattern='Cort', replacement='CC_Cort'))
#df_164$IMAGE.NAME = unlist(lapply(df_164$IMAGE.NAME, gsub, pattern='Tc_12', replacement='TC_12'))
## Missing
#df_164 = df_164[df_164$IMAGE.NAME != 'Layer1', ]
#df_164 = df_164[df_164$IMAGE.NAME != 'TC_1', ]
#df_164 = df_164[df_164$IMAGE.NAME != 'TC_18', ]
#df_164 = df_164[df_164$IMAGE.NAME != 'TC_19', ]
#df_164$IMAGE.NAME = toupper(df_164$IMAGE.NAME)
unique(df_164$IMAGE.NAME)
 [1] "CC_8"         "CC_10"        "CC_Cortical1" "CC_Cortical2" "CC_L2-1"      "CC_L2-2"      "CC_L2-3"     
 [8] "CC_2"         "CC_3"         "CC_4"         "CC_5"         "CC_6"         "CC_7"         "CC_9"        
[15] "TC_1"         "TC_2"         "TC_3"         "TC_4"         "TC_5"         "TC_6"         "TC_7"        
[22] "TC_8"         "TC_9"         "TC_10"        "TC_Cortical1" "TC_Cortical2" "TC_Cortical3"

Now we know that everything is equal to one another, we should load the variable

image_names = unique(df_164$IMAGE.NAME)
# Preset these variables to negative values so I can easily check if they were updated later
df_164$X = -1
df_164$Y = -1
df_164$clump = NaN
clump_header = '164_'
clump_files = list.files(clump_dir)
# set some normalization variables
## This is the size of the image when the pixel values are taken from top left down
IMAGE_SIZE = 1024
## This is the size of an image in the global coordinate space
IMAGE_LEN = 32
TC_IMAGE_HEIGHT = 410
TC_IMAGE_WIDTH = 446
CC_IMAGE_HEIGHT= 422
CC_IMAGE_WIDTH = 214

# Load the dataframe with global and relative coordinates
img_cords = read.table(file.path(meta_dir, '164_pixel_coordinates.csv'), sep = ',', header = TRUE)

images = list.files(meta_dir)
for(image_name in image_names){
    if(grepl('408', image_name)){
      next
    }
    print(image_name)
    split_names = strsplit(image_name, '_')
    cortex = toupper(split_names[[1]][1])
    number = split_names[[1]][2]
    number_csv = paste0('_', number, '.csv')
    filename = images[grepl(cortex, images) & grepl(number_csv, images) & grepl('164', images)]
    coordinates = read.table(file.path(meta_dir, filename), sep = ',', header = TRUE)
    
    ## Get the clumps
    filename = clump_files[grepl(clump_header, clump_files) & grepl(paste0(image_name, '_'),clump_files)]
    if(length(filename != 0)){
      clump_df = as.data.frame(t(read.csv(file.path(clump_dir, filename), header = FALSE)))
      colnames(clump_df) = c('roi', 'cluster')
      clump_df$roi = as.numeric(clump_df$roi)
      
      if(image_name == "CC_L2-1"){
        coordinates = coordinates[c(1:37, 39:nrow(coordinates)), ]
        if(38 %in% clump_df$roi){clump_df = clump_df[clump_df$roi != 38, ]}
        clump_df$roi[clump_df$roi > 37] = clump_df$roi[clump_df$roi > 37] - 1
      }
      
      image_idxs = which(df_164$IMAGE.NAME == image_name)
      clump_df$roi_idxs = image_idxs[1] + clump_df$roi - 1
      df_164[clump_df$roi_idxs, "clump"] = paste0(clump_header, image_name, '_', clump_df$cluster)
    } else{
      print('No clumps!')
      print(image_name)
    }
    
    if(cortex == 'CC'){ 
      print(paste('cc', filename, image_name))
      ## So if CC, we add the coordinates for TC_1 to overall image coordinates
      x_adj = img_cords[img_cords$Name == 'TC_1', 'x'] + 
                img_cords[img_cords$Name == 'G_CC1_to_TC1', 'x']
      ## Start from bottom, add the height, subtract TC_1 height, and then global CC1 to TC1
      y_adj = TC_IMAGE_HEIGHT - img_cords[img_cords$Name == 'TC_1', 'y'] +
                img_cords[img_cords$Name == 'G_CC1_to_TC1', 'y'] + CC_IMAGE_HEIGHT
    }else{
      print(paste('tc', filename, image_name))
      x_adj = 0
      y_adj = TC_IMAGE_HEIGHT
    }
    
    ## So don't do repelled for now
    #x_repelled <- (512 - coordinates$X_Coordinate_In_pixels)
    
    ## so the resized x distance is from left, so just add to the box location and adj
    df_164[df_164$IMAGE.NAME == image_name, 'X'] = (coordinates$X_Coordinate_In_pixels / 
                                                      IMAGE_SIZE * IMAGE_LEN) + 
                                                    img_cords[img_cords$Name == image_name, 'x'] + x_adj
    ## resized y distance
    df_164[df_164$IMAGE.NAME == image_name, 'Y'] = y_adj - img_cords[img_cords$Name == image_name, 'y'] - 
                                                      (coordinates$Y_Coordinate_In_pixels / IMAGE_SIZE * 
                                                      IMAGE_LEN)  
}
[1] "CC_8"
[1] "cc 164_CC_ROI_CC_8_clumps.csv CC_8"
[1] "CC_10"
[1] "cc 164_CC_ROI_CC_10_clumps.csv CC_10"
[1] "CC_Cortical1"
[1] "cc 164_CC_ROI_CC_Cortical1_clumps.csv CC_Cortical1"
[1] "CC_Cortical2"
[1] "cc 164_CC_ROI_CC_Cortical2_clumps.csv CC_Cortical2"
[1] "CC_L2-1"
[1] "cc 164_CC_ROI_CC_L2-1_clumps.csv CC_L2-1"
[1] "CC_L2-2"
[1] "cc 164_CC_ROI_CC_L2-2_clumps.csv CC_L2-2"
[1] "CC_L2-3"
[1] "cc 164_CC_ROI_CC_L2-3_clumps.csv CC_L2-3"
[1] "CC_2"
[1] "No clumps!"
[1] "CC_2"
[1] "cc  CC_2"
[1] "CC_3"
[1] "cc 164_CC_ROI_CC_3_clumps.csv CC_3"
[1] "CC_4"
[1] "cc 164_CC_ROI_CC_4_clumps.csv CC_4"
[1] "CC_5"
[1] "cc 164_CC_ROI_CC_5_clumps.csv CC_5"
[1] "CC_6"
[1] "cc 164_CC_ROI_CC_6_clumps.csv CC_6"
[1] "CC_7"
[1] "cc 164_CC_ROI_CC_7_clumps.csv CC_7"
[1] "CC_9"
[1] "cc 164_CC_ROI_CC_9_clumps.csv CC_9"
[1] "TC_1"
[1] "tc 164_TC_ROI_TC_1_clumps.csv TC_1"
[1] "TC_2"
[1] "tc 164_TC_ROI_TC_2_clumps.csv TC_2"
[1] "TC_3"
[1] "tc 164_TC_ROI_TC_3_clumps.csv TC_3"
[1] "TC_4"
[1] "tc 164_TC_ROI_TC_4_clumps.csv TC_4"
[1] "TC_5"
[1] "tc 164_TC_ROI_TC_5_clumps.csv TC_5"
[1] "TC_6"
[1] "tc 164_TC_ROI_TC_6_clumps.csv TC_6"
[1] "TC_7"
[1] "tc 164_TC_ROI_TC_7_clumps.csv TC_7"
[1] "TC_8"
[1] "tc 164_TC_ROI_TC_8_clumps.csv TC_8"
[1] "TC_9"
[1] "tc 164_TC_ROI_TC_9_clumps.csv TC_9"
[1] "TC_10"
[1] "tc 164_TC_ROI_TC_10_clumps.csv TC_10"
[1] "TC_Cortical1"
[1] "tc 164_TC_ROI_TC_Cortical1_clumps.csv TC_Cortical1"
[1] "TC_Cortical2"
[1] "tc 164_TC_ROI_TC_Cortical2_clumps.csv TC_Cortical2"
[1] "TC_Cortical3"
[1] "tc 164_TC_ROI_TC_Cortical3_clumps.csv TC_Cortical3"

We have the coordinates for 408_TC and others

rownames(df_164) = 1:nrow(df_164)
jy_164 = df_164 %>%
  dplyr::select(-c(area, IMAGE.NAME, X, Y, clump)) %>%
  t() %>%
  CreateSeuratObject()

just set everything from below 1 in ratio to zero

jy_164 <- NormalizeData(jy_164, scale.factor = 1e5) ###
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
normed = GetAssayData(jy_164, slot = 'data')
normed[normed < 3] = 0
for(gene_name in rownames(jy_164)) {
  mdn_gene_expr = median(normed[gene_name, normed[gene_name, ] > 0])
  #print(gene_name)
  #print(mdn_gene_expr)
  #normed[gene_name, normed[gene_name, ] < mdn_gene_expr] = 0
}
jy_164 <- SetAssayData(jy_164, slot = 'data', normed)
jy_164 <- FindVariableFeatures(jy_164, selection.method = "vst")
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
all.genes <- rownames(jy_164)
jy_164 <- ScaleData(jy_164, features = all.genes)
Centering and scaling data matrix

  |                                                                                                                
  |                                                                                                          |   0%
  |                                                                                                                
  |==========================================================================================================| 100%
jy_164 <- RunPCA(jy_164, approx = FALSE)
Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.PC_ 1 
Positive:  NKX2.1, MAF1, SCGN, PROX1, GAD1, SP8, VIP, LHX6, GSX2, DCX 
       TSHZ1, CXCR7, COUPTF2, SST, DLX2, LRP8 
Negative:  KIA0319, SATB2, ASCL1, PAX6, CALB2, DCDC2, RELN, NCAM1, CXCL12, TBR1 
       EMX1, EGFR, CXCL14, VLDLR, CXCR4, EOMES 
PC_ 2 
Positive:  DCX, SCGN, SP8, TBR1, EGFR, SATB2, DCDC2, EOMES, NKX2.1, LRP8 
       KIA0319, NCAM1, EMX1, CXCR7, TSHZ1, CALB2 
Negative:  GAD1, VIP, CXCR4, LHX6, MAF1, DLX2, SST, RELN, CXCL12, CXCL14 
       COUPTF2, PROX1, PAX6, GSX2, ASCL1, VLDLR 
PC_ 3 
Positive:  COUPTF2, SP8, PROX1, SCGN, EOMES, TBR1, MAF1, CXCR7, NKX2.1, GSX2 
       TSHZ1, LHX6, DCDC2, EGFR, VIP, LRP8 
Negative:  SST, ASCL1, PAX6, RELN, CALB2, CXCL14, EMX1, CXCL12, DLX2, NCAM1 
       GAD1, CXCR4, KIA0319, DCX, VLDLR, SATB2 
PC_ 4 
Positive:  TSHZ1, PROX1, SP8, ASCL1, DLX2, VLDLR, NKX2.1, NCAM1, PAX6, CXCR7 
       SCGN, EMX1, RELN, CXCL14, VIP, KIA0319 
Negative:  LRP8, TBR1, EOMES, CALB2, LHX6, GSX2, COUPTF2, DCX, MAF1, GAD1 
       CXCL12, DCDC2, SATB2, EGFR, CXCR4, SST 
PC_ 5 
Positive:  PAX6, COUPTF2, RELN, TSHZ1, DLX2, SP8, CXCL12, SCGN, TBR1, GSX2 
       LRP8, DCX, SATB2, LHX6, ASCL1, GAD1 
Negative:  NKX2.1, DCDC2, EOMES, VLDLR, NCAM1, SST, CXCR7, EMX1, CXCR4, EGFR 
       MAF1, KIA0319, VIP, PROX1, CXCL14, CALB2 
jy_164 <- FindNeighbors(jy_164, dims = 1:30)
Computing nearest neighbor graph
Computing SNN
jy_164 <- FindClusters(jy_164, resolution = 0.8)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 802
Number of edges: 30100

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.6519
Number of communities: 6
Elapsed time: 0 seconds
jy_164 <- RunUMAP(jy_164, dims = 1:30)
15:46:44 UMAP embedding parameters a = 0.9922 b = 1.112
15:46:44 Read 802 rows and found 30 numeric columns
15:46:44 Using Annoy for neighbor search, n_neighbors = 30
15:46:44 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:44 Writing NN index file to temp file /tmp/RtmpaBJ93n/file834751395e2c
15:46:44 Searching Annoy index using 1 thread, search_k = 3000
15:46:45 Annoy recall = 100%
15:46:45 Commencing smooth kNN distance calibration using 1 thread
15:46:46 Initializing from normalized Laplacian + noise
15:46:46 Commencing optimization for 500 epochs, with 30056 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:47 Optimization finished
jy_164$clump <- df_164$clump

DimPlot(jy_164,  reduction = "umap", group.by = 'seurat_clusters') + NoAxes()

DimPlot(jy_164,  reduction = "umap", group.by = 'clump') + NoAxes() + NoLegend()

unique(df_164$IMAGE.NAME)
 [1] "CC_8"         "CC_10"        "CC_Cortical1" "CC_Cortical2" "CC_L2-1"      "CC_L2-2"      "CC_L2-3"     
 [8] "CC_2"         "CC_3"         "CC_4"         "CC_5"         "CC_6"         "CC_7"         "CC_9"        
[15] "TC_1"         "TC_2"         "TC_3"         "TC_4"         "TC_5"         "TC_6"         "TC_7"        
[22] "TC_8"         "TC_9"         "TC_10"        "TC_Cortical1" "TC_Cortical2" "TC_Cortical3"
images_ordered = c('TC_Cortical3', 'TC_Cortical2', 'TC_Cortical1', 'TC_10', 'TC_9', 'TC_8', 'TC_7', 'TC_6', 'TC_5', 'TC_4', 'TC_3', 'TC_2','TC_1','CC_2','CC_3',
           'CC_4', 'CC_5', 'CC_6', 'CC_7', 'CC_8', 'CC_9', 'CC_10',
           'CC_L2-1', 'CC_L2-2', 'CC_L2-3', 'CC_Cortical1', 'CC_Cortical2')
x_horz = 1:length(images_ordered) * 35
y_horz = rep(0, length(images_ordered))
horz_embedding = data.frame()
df_164$X_horz = -1
df_164$Y_horz = -1

images = list.files(meta_dir)
for(i in 1:length(images_ordered)){
    image_name = images_ordered[i]
    print(image_name)
    split_names = strsplit(image_name, '_')
    cortex = toupper(split_names[[1]][1])
    number = split_names[[1]][2]
    number_csv = paste0('_', number, '.csv')
    filename = images[grepl(cortex, images) & grepl(number_csv, images) & grepl('164', images)]
    coordinates = read.table(file.path(meta_dir, filename), sep = ',', header = TRUE)
    if(image_name == "CC_L2-1"){
      coordinates = coordinates[c(1:37, 39:nrow(coordinates)), ]
    }
    ## checked already that lists are equal, missing 1, 18, 19 for now, layer 1 and others
 
    ## so this is a little tricky, so need to get it right
    ## Remember, it is the top right that the coordinate is coming from, but
    ## the bottom right is the new coordinate space.
    ## so first when we get the original coordinate space, to set to relative
    ## of bottom would be the same X, but 1024 - Y
    
    ## push out the coordinates for better visualization
    #x_repelled <- (512 - coordinates$X_Coordinate_In_pixels)
    
    
    df_164[df_164$IMAGE.NAME == image_name, 'X_horz'] = (coordinates$X_Coordinate_In_pixels / 
                                                      IMAGE_SIZE * IMAGE_LEN) + y_horz[i]
    df_164[df_164$IMAGE.NAME == image_name, 'Y_horz'] = ((1024-coordinates$Y_Coordinate_In_pixels) / 
                                                      IMAGE_SIZE * IMAGE_LEN) + x_horz[i]
}
[1] "TC_Cortical3"
[1] "TC_Cortical2"
[1] "TC_Cortical1"
[1] "TC_10"
[1] "TC_9"
[1] "TC_8"
[1] "TC_7"
[1] "TC_6"
[1] "TC_5"
[1] "TC_4"
[1] "TC_3"
[1] "TC_2"
[1] "TC_1"
[1] "CC_2"
[1] "CC_3"
[1] "CC_4"
[1] "CC_5"
[1] "CC_6"
[1] "CC_7"
[1] "CC_8"
[1] "CC_9"
[1] "CC_10"
[1] "CC_L2-1"
[1] "CC_L2-2"
[1] "CC_L2-3"
[1] "CC_Cortical1"
[1] "CC_Cortical2"
hcoords = df_164 %>% dplyr::select(c('X_horz', 'Y_horz')) %>% as.matrix()
colnames(hcoords) <- c('pixel_1', 'pixel_2')

jy_164[["H"]] <- CreateDimReducObject(embeddings = hcoords, key = "pixel_", assay = DefaultAssay(jy_164))
jy_164<- RenameCells(jy_164, c(outer('164_', 1:ncol(jy_164), FUN=paste0)))
jy_164$area = df_164$area
jy_408<- RenameCells(jy_408, c(outer('408_', 1:ncol(jy_408), FUN=paste0)))
jy_408$area = df_408$area
jy_all <- merge(jy_164, jy_408)
jy_all <- NormalizeData(jy_all, scale.factor = 1e5) ###
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
normed = GetAssayData(jy_all, slot = 'data')
normed[normed < 3] = 0
for(gene_name in rownames(jy_all)) {
  if (gene_name == 'DCX'){
    mdn_gene_expr = 0.5
    print('skip dcx')
  } else if (!gene_name %in% c('COUPTF2', 'SP8')){
    mdn_gene_expr = median(normed[gene_name, normed[gene_name, ] > 0])
  }else{
    mdn_gene_expr = quantile(normed[gene_name, normed[gene_name, ] > 0], .40)
  }

  normed[gene_name, normed[gene_name, ] < mdn_gene_expr] = 0
}
[1] "skip dcx"
jy_all <- SetAssayData(jy_all, slot = 'data', normed)
jy_all <- FindVariableFeatures(jy_all, selection.method = "vst")
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
all.genes <- rownames(jy_all)
jy_all <- ScaleData(jy_all, features = all.genes)
Centering and scaling data matrix

  |                                                                                                                
  |                                                                                                          |   0%
  |                                                                                                                
  |==========================================================================================================| 100%
jy_all <- RunPCA(jy_all, approx = FALSE)
Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.Warning: Requested number is larger than the number of available items (32). Setting to 32.PC_ 1 
Positive:  DCX, TBR1, KIA0319, LRP8, SATB2, EOMES, DCDC2, EGFR, NCAM1, ASCL1 
       CALB2, VLDLR, PAX6, EMX1, COUPTF2, CXCR7 
Negative:  GAD1, VIP, LHX6, SST, MAF1, CXCR4, NKX2.1, DLX2, PROX1, TSHZ1 
       GSX2, RELN, SP8, CXCL14, CXCL12, SCGN 
PC_ 2 
Positive:  SCGN, TSHZ1, DCX, NKX2.1, SP8, CXCR7, PROX1, GSX2, COUPTF2, TBR1 
       LRP8, VLDLR, CXCL14, EGFR, NCAM1, MAF1 
Negative:  ASCL1, CXCR4, KIA0319, SATB2, CALB2, CXCL12, VIP, RELN, PAX6, DLX2 
       GAD1, DCDC2, SST, EMX1, EOMES, LHX6 
PC_ 3 
Positive:  NCAM1, TSHZ1, MAF1, VLDLR, PROX1, CXCR7, CXCL14, RELN, GSX2, ASCL1 
       PAX6, EGFR, CXCR4, VIP, DCDC2, NKX2.1 
Negative:  CALB2, TBR1, LRP8, CXCL12, DCX, DLX2, COUPTF2, SCGN, SST, EOMES 
       LHX6, SP8, SATB2, GAD1, KIA0319, EMX1 
PC_ 4 
Positive:  TBR1, LRP8, GAD1, EGFR, LHX6, VIP, CXCR4, CXCL14, NCAM1, DCDC2 
       DCX, EOMES, SST, CXCR7, MAF1, RELN 
Negative:  SP8, DLX2, PAX6, SCGN, CALB2, NKX2.1, EMX1, ASCL1, SATB2, COUPTF2 
       KIA0319, CXCL12, PROX1, GSX2, VLDLR, TSHZ1 
PC_ 5 
Positive:  RELN, GSX2, PAX6, MAF1, SST, SCGN, LHX6, TBR1, EGFR, LRP8 
       ASCL1, SP8, EMX1, SATB2, VIP, DCX 
Negative:  PROX1, CALB2, VLDLR, TSHZ1, EOMES, DLX2, DCDC2, CXCR4, CXCL12, GAD1 
       CXCR7, NCAM1, CXCL14, COUPTF2, NKX2.1, KIA0319 
jy_all <- FindNeighbors(jy_all, dims = 1:30)
Computing nearest neighbor graph
Computing SNN
jy_all <- FindClusters(jy_all, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 1815
Number of edges: 61161

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8215
Number of communities: 7
Elapsed time: 0 seconds
jy_all <- RunUMAP(jy_all, dims = 1:30)
15:46:50 UMAP embedding parameters a = 0.9922 b = 1.112
15:46:50 Read 1815 rows and found 30 numeric columns
15:46:50 Using Annoy for neighbor search, n_neighbors = 30
15:46:50 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:50 Writing NN index file to temp file /tmp/RtmpaBJ93n/file8347566dc315
15:46:50 Searching Annoy index using 1 thread, search_k = 3000
15:46:50 Annoy recall = 100%
15:46:51 Commencing smooth kNN distance calibration using 1 thread
15:46:52 Initializing from normalized Laplacian + noise
15:46:52 Commencing optimization for 500 epochs, with 70670 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:46:54 Optimization finished
DimPlot(jy_all,  reduction = "umap", group.by = 'seurat_clusters') + NoAxes()

DimPlot(jy_all,  reduction = "umap", group.by = 'area') + NoAxes() # NoLegend()

FeaturePlot(jy_all, c('CXCL12', 'CXCR4'), cells = which(FetchData(jy_all, 'GAD1') > 0), cols = c( '#F18F01', '#048BA8'), blend= TRUE) 
Warning: Only two colors provided, assuming specified are for features and agumenting with 'lightgrey' for double-negatives

new.cluster.ids = c('CGE/LGE',
                    'Ex',
                    'TBR1+ CGE',
                    'CALB2+DLX2+',
                    'VIP+GAD1+',
                    'SST+LHX6+',
                    'MGE')
names(new.cluster.ids) <- levels(jy_all)
jy_all <- RenameIdents(jy_all, new.cluster.ids)

What are my nearest neighbors?

Let’s just look at it simply. What do clusters look like?

What cell types are in those clusters?

Definitely a clump graph

Stacked barplots is a way to go

plot_cluster_fraction <- function(sobj, clump_id){
  all_idents = levels(Idents(sobj))
  def_colors = hue_pal()(length(all_idents))
  
  clump_obj <- sobj[,sobj$clump == clump_id]
  clstrs <- as.character(Idents(clump_obj))
  clmp_ids <- clump_obj$clump
  clmp_df <- as.data.frame(cbind(clstrs, clmp_ids))
  cell_count = nrow(clmp_df)
  colnames(clmp_df) <- c('cluster', 'clump')
  
  which_colors = def_colors[which(all_idents %in% clstrs)]

  clmp_df %>% dplyr::count(clump, cluster) %>%
    ggplot(aes(x="", y=n, fill=cluster))+ 
  geom_bar(stat="identity", width=1, color="white") +
  coord_polar("y", start=0) +
    ggtitle(paste0(clump_id, ', n=', cell_count)) +
    scale_fill_manual(values = which_colors) +
  theme_void()+ NoLegend() + theme(title = element_text(face = 'bold', size = rel(0.8), hjust = 1)) # remove background, grid, numeric labels
}
plot_cluster_fraction(jy_all, '164_CC_8_0')
clmps = unique(jy_all$clump)
plots <- lapply(1:length(clmps), function(i){
    plot_cluster_fraction(jy_all, clmps[i])
  })
umaps = plot_grid(plotlist = plots, label_size = 3, nrow = 23)
umaps

try a stacked barplot

aClumpDF <- as.data.frame(cbind(as.character(Idents(jy_all)), jy_all$clump, jy_all$area))
colnames(aClumpDF) <- c('ctype', 'clump', 'area')

g1 <- aClumpDF %>% filter(clump != NaN) %>%
  dplyr::count(clump, ctype) %>% 
ggplot(aes(clump, n, fill=ctype)) +
  geom_bar(stat="identity")
g2 <- aClumpDF %>% filter(clump == NaN) %>%
  dplyr::count(clump, ctype) %>% 
ggplot(aes(ctype, y = n, fill = ctype)) +
  geom_bar(stat="identity") + RotatedAxis() + NoLegend()
g2
aClumpDF %>% filter(clump != NaN) %>%
  dplyr::count(clump, ctype) %>% 
ggplot(aes(ctype, y = n, fill = ctype)) +
  geom_bar(stat="identity") + RotatedAxis() + NoLegend()
## I want to summarize the ratio of nan to non-nan
aClumpDF %>% filter(clump != NaN) %>%
  dplyr::count(ctype, area, clump) %>% 
ggplot(aes(area, n, fill=ctype)) + RotatedAxis() +
  geom_bar(stat="identity")
aClumpDF %>% filter(clump != NaN) %>%
  dplyr::count(area, clump) %>%
   ggplot(aes(area, n)) + 
  geom_boxplot() + RotatedAxis()
Mode <- function(x) {
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

aClumpDF %>% group_by(clump) %>% mutate(top_ctype=Mode(ctype))
aClumpDF$top_ctype <-  with(aClumpDF, ave(ctype, clump, FUN=Mode))
aClumpDF$top_ctype

 
# Create data
#clusters <- aClumpDF %>% filter(grepl('MS', area)) %>% dplyr::count(area, clump) %>% filter(clump != NaN)
clusters <- aClumpDF %>% dplyr::count(ctype, clump) %>% filter(clump != NaN)
 
# Generate the layout. This function return a dataframe with one line per bubble. 
# It gives its center (x and y) and its radius, proportional of the value
packing <- circleProgressiveLayout(clusters$n, sizetype='area')
 
# We can add these packing information to the initial data frame
clusters <- cbind(clusters, packing)
 
# Check that radius is proportional to value. We don't want a linear relationship, since it is the AREA that must be proportionnal to the value
# plot(data$radius, data$value)
 
# The next step is to go from one center + a radius to the coordinates of a circle that
# is drawn by a multitude of straight lines.
clusters.gg <- circleLayoutVertices(packing, npoints=50)
clusters.gg$idents <- rep(clusters$ctype, each=51)
 
# Make the plot
ggplot() + 
  
  # Make the bubbles
  geom_polygon(data = clusters.gg, aes(x, y, group = id, fill=as.factor(idents)), colour = "black", alpha = 0.9) +
  
  # Add text in the center of each bubble + control its size
  geom_text(data = clusters, aes(x, y, size=n, label = clump)) +
  scale_size_continuous(range = c(1,3)) +
  
  # General theme:
  theme_void() + 
  theme(legend.position="none") +
  coord_equal()
clump_meta <- data.frame()

jy_clump = jy_all[, jy_all$clump!= NaN]
tic()
for(i in 1:ncol(jy_clump)){
  # Iterate through the cells, get the neighbors for that particular cell
  cell <- colnames(jy_clump[,i])
  clump <- jy_clump[, jy_clump$clump == jy_clump$clump[i]]
  neighbors <- clump@meta.data %>% filter(clump != cell)
  probs = rep(0, length(levels(jy_all)))
  neighbor_ids <- as.numeric(neighbors$seurat_clusters)
  # Calculate the probability of each cluster type
  for(j in 1:length(unique(neighbor_ids))){
    j_class = unique(neighbor_ids)[j]
    probs[j_class] = sum(neighbor_ids == j_class) / nrow(neighbors)
  }

  clump_meta <- rbind(clump_meta, probs)
  colnames(clump_meta) <- c(outer('p', 1:length(levels(jy_all)), FUN=paste0))
}
toc()
clump_meta
clumps <- as.data.frame(cbind(jy_clump@meta.data, clump_meta))
ref_cluster = 1
plots = list()

for(i in 1:length(levels(unique(clumps$seurat_clusters)))){
  ref_cluster = i
  plots[[i]] <- clumps %>%
  filter(as.numeric(seurat_clusters) == ref_cluster) %>%
  dplyr::select(c(seurat_clusters, p1, p2, p3, p4, p5, p6,p7,p8,p9,p10,p11,p12,p13)) %>%
  pivot_longer(!seurat_clusters, names_to = "classes", values_to = "prob") %>%
  ggplot(aes(x = prob, fill = factor(classes, levels = c(outer('p', 1:length(levels(jy_all)), FUN=paste0))))) + geom_boxplot(notch = FALSE) + 
  ggtitle(paste(levels(jy_all)[ref_cluster], "vs other")) + scale_fill_discrete(name = 'subtypes', labels=levels(Idents(jy_all)))
 }

plot_grouped <- marrangeGrob(plots, nrow=2, ncol=2)
ggsave(filename = 'cell_specific_clustering.pdf', path = file.path(output_dir_plot, '20220808_1'), plot_grouped, height = 10, width = 14, dpi = 150)
ref_cluster = 2

clumps %>%
  filter(cluster == ref_cluster) %>%
  select(c(image, p0, p1, p2, p3, p4, p5, p6)) %>%
  pivot_longer(!cluster, names_to = "classes", values_to = "prob") %>%
  ggplot(aes(x = prob, color = classes)) + geom_boxplot() + 
  ggtitle(paste("Cluster",as.character(ref_cluster-1), "vs other"))
pmat <- clumps %>%
    group_by(seurat_clusters) %>%
    dplyr::select(c(p1, p2, p3, p4, p5, p6,p7,p8,p9,p10,p11,p12,p13)) %>%
    dplyr::summarise(across(everything(), list(mean))) %>%
    dplyr::select(-seurat_clusters) %>%
    as.matrix()

rownames(pmat) <- levels(Idents(jy_all))
colnames(pmat) <- rownames(pmat)
pheatmap(pmat, color = turbo(n = 50))
library("viridis")           # Load
pheatmap(pmat,color = rocket(n = 50), cluster_rows = FALSE, cluster_cols = FALSE)
## I want to know the expression of different genes by the different areas
## and clumps in those respective areas

## So basically 4 plots, anterior dorsal, ventral, and posterior
## Then need the clump name per thing-a-ma-bob
## Clump versus no clump
## differences between clumps (top_ctype per clump)
## Start with the barcharts

plot_functional_genes <- function(sobj, group_key, group = 'area', fxn_gene_list = NULL) {
  ## So I need to make a barchart with the average expression of the particular
  ## gene list the I care about. This can be hardcoded... here?
  if(is.null(fxn_gene_list)){
    fxn_gene_list = c('VLDLR', 'LRP8', 'CXCR4', 'CXCR7', 'CXCL12', 'CXCL14', 'NCAM1')
  }
  sbset_obj = sobj[, sobj[[group]] == group_key]
  ## I want to plot their "clumpedness," so really it's either clump or no clump as a group
  gene_expr = as.data.frame(FetchData(sbset_obj, fxn_gene_list))
  gene_expr$clump = sbset_obj$clump != "NaN"
  gene_expr %>%
    pivot_longer(!clump, names_to = 'gene', values_to = 'expr') %>%
    ggplot(aes(x = gene, y = expr, fill = clump)) +
    geom_boxplot() + theme_classic() + ggtitle(group_key) + RotatedAxis()
}
plot_functional_genes(jy_all, '164_TC')

## For areas
area_list = unique(jy_all$area)
plots = list()
plots <- lapply(1:length(area_list), function(i){
    plot_functional_genes(jy_all, area_list[i])
  })
vlnplots = plot_grid(plotlist = plots, label_size = 3, nrow = 9)
vlnplots

#ggsave(filename = 'img_fxnal_gene_clumps.pdf', path = file.path(output_dir_plot, '20220817_1'), vlnplots, height = 9, width = 12, dpi = 150)
## for images
jy_all$image = c(df_164$IMAGE.NAME, df_408$IMAGE.NAME)
image_list = unique(jy_all$image)
plots = list()
plots <- lapply(1:length(image_list), function(i){
    plot_functional_genes(jy_all, image_list[i], group = 'image')
  })
bxplots = plot_grid(plotlist = plots, label_size = 3, nrow = 12)
bxplots

ggsave(filename = 'img_fxnal_gene_clumps.pdf', path = file.path(output_dir_plot, '20220817_1'), bxplots, height = 18, width = 12, dpi = 150)
plot_clump_expr <- function(sobj, clump_name, gene, pt_size = 8, IMAGE_SPECIFIC = FALSE) {

  sbset_obj = sobj[, df_408$IMAGE.NAME == clump_name]
  xy = Embeddings(sbset_obj, reduction = 'H')
  #expmat  = as.character(Idents(sbset_obj))
  expmat = FetchData(sbset_obj, gene)
  df <- as.data.frame(cbind(xy, expmat))
  colnames(df) <- c('x', 'y', 'ident')
  ligands = c('CXCL12', 'CXCL14', 'RELN')
  receptors = c('CXCR4', 'CXCR7', 'LRP8', 'VLDLR', 'EGFR')
  maturity = c('VIP', 'SST')
  adhesion = c('NCAM1')
  immaturity = c('DCX')
  disease = c('DCDC2', 'KIA0319')
  
  color_df = as.data.frame(rownames(sobj))
  colnames(color_df) = 'gene'
  color_df$color = '#0768FA'
  color_df$color[color_df$gene %in% ligands] = '#FFFB46'
  color_df$color[color_df$gene %in% receptors] = '#F26419'
  color_df$color[color_df$gene %in% maturity] = '#450920'
  color_df$color[color_df$gene %in% adhesion] = '#F42C04'
  color_df$color[color_df$gene %in% immaturity] = '#0CF700'
  color_df$color[color_df$gene %in% disease] = '#8FB356'
  
  
  if (IMAGE_SPECIFIC){
    title_name = clump_name
  } else{
    title_name = gene
  }
  
  #colors = hue_pal()(length(levels(Idents(jy_all))))
  #colors = colors[which(levels(Idents(jy_all)) %in% df$ident)]
  colors = c('grey90', 'grey90', color_df$color[color_df$gene == gene])
  df$x = as.numeric(df$x)
  df$y = as.numeric(df$y)
  df$clump = sbset_obj$clump
  df$ident_label = as.character(round(df$ident, digits = 2))
  df %>%
    #ggplot(aes(x = x, y = y, color = clump)) +
    #geom_point(size = 8) + theme_classic() + ggtitle(clump_name) + coord_fixed(ratio = 1) + NoAxes()  ## without cell type
    #ggplot(aes(x = x, y = y, color = factor(ident, levels = levels(Idents(sobj))))) +
    ggplot(aes(x = x, y = y, color = ident)) +
    geom_point(size = pt_size) + theme_classic() + ggtitle(title_name) + coord_fixed(ratio = 1) + NoAxes() + #scale_color_manual(values = colors) +
    geom_encircle(data = filter(df, clump != "NaN"), expand = 0.03,aes(group = clump) ) + scale_color_gradient(na.value = colors[1], low = colors[2], high = colors[3]) #+ geom_label_repel(data = filter(df, ident > 0), label = filter(df, ident > 0)[['ident_label']]) 
  #+ scale_color_gradient(limits = c(0,11))

}

plot_clump_expr(jy_408, 'TC_9', 'GAD1')
plot_clump_clust <- function(sobj, clump_name, pt_size = 8) {

  sbset_obj = sobj[, df_408$IMAGE.NAME == clump_name]
  xy = Embeddings(sbset_obj, reduction = 'H')
  #expmat  = as.character(Idents(sbset_obj))
  expmat = FetchData(sbset_obj, gene)
  df <- as.data.frame(cbind(xy, expmat))
  colnames(df) <- c('x', 'y', 'ident')
  
  #colors = hue_pal()(length(levels(Idents(jy_all))))
  #colors = colors[which(levels(Idents(jy_all)) %in% df$ident)]
  colors = c('grey90', 'grey90', color_df$color[color_df$gene == gene])
  df$x = as.numeric(df$x)
  df$y = as.numeric(df$y)
  df$clump = sbset_obj$clump
  df %>%
    ggplot(aes(x = x, y = y, color = clump)) +
    geom_point(size = pt_size) + theme_classic() + ggtitle(clump_name) + coord_fixed(ratio = 1) + NoAxes() +  ## without cell type
    #ggplot(aes(x = x, y = y, color = factor(ident, levels = levels(Idents(sobj))))) +
    #ggplot(aes(x = x, y = y, color = ident)) +
    #geom_point(size = 8) + theme_classic() + ggtitle(clump_name) + coord_fixed(ratio = 1) + NoAxes() + #scale_color_manual(values = colors) +
    geom_encircle(data = filter(df, clump != "NaN"), expand = 0.03,aes(group = clump) )

}

plot_clump_clust(jy_408, 'TC_11')
#jy_164$image = jy_all$image[1:ncol(jy_164)]
plot_clump_celltype(jy_408, 'TC_3', pt_size = 3)
Error in `f()`:
! Insufficient values in manual scale. 5 needed but only 0 provided.
Backtrace:
  1. base (local) `<fn>`(x)
  2. ggplot2:::print.ggplot(x)
  4. ggplot2:::ggplot_build.ggplot(x)
  5. base::lapply(data, scales_map_df, scales = npscales)
  6. ggplot2 (local) FUN(X[[i]], ...)
     ...
 13. ggplot2 (local) FUN(X[[i]], ...)
 14. self$map(df[[j]])
 15. ggplot2 (local) f(..., self = self)
 16. self$palette(n)
 17. ggplot2 (local) f(...)

dev.off()
Error in dev.off() : cannot shut down device 1 (the null device)
dapi_img = image_read('/home/aunoy/st/arc_profiling/st_analysis/hand_annotated_data/test/408_tc3.tif')
myplot <- plot_clump_celltype(jy_408, 'TC_3', pt_size = 5)
ggdraw() +
  draw_image(dapi_img) +
  draw_plot(myplot, scale = 1.14)

normed = GetAssayData(jy_408, slot = "data")
plots = list()
plots <- lapply(1:length(unique(df_408$IMAGE.NAME)), function(i){
  plot_clump_expr(jy_408, unique(df_408$IMAGE.NAME)[i], 'COUPTF2', pt_size = 8, IMAGE_SPECIFIC = TRUE)
})
all_plots = plot_grid(plotlist = plots, label_size = 1, nrow = 6)
all_plots
ggsave(filename = paste0('408_COUPTF2_clump_profiles.pdf'), path = file.path(output_dir_plot, '20220823_1'), all_plots, 
         height = 18, width = 18)
sum(FetchData(jy_all, 'GAD1') != 0) / sum(FetchData(jy_all, 'GAD1') == 0)
DimPlot(jy_all) + NoAxes()
FeaturePlot(jy_all, cells = which(Idents(jy_all) == 'VIP+GAD1+'), c('COUPTF2', 'SP8', 'PROX1', 'DLX2'), cols = c('lightgrey', hue_pal()(9)[5]))
FeaturePlot(jy_all, cells = which(Idents(jy_all) == 'VIP+GAD1+'), c('VIP', 'LHX6', 'NKX2.1', 'GAD1'), cols = c('lightgrey', hue_pal()(9)[5]))
FeaturePlot(jy_all,  c('COUPTF2', 'SP8', 'PROX1', 'DLX2'), cols = c('lightgrey', hue_pal()(9)[5]))
FeaturePlot(jy_all, c('NKX2.1', 'LHX6', 'MAF1', 'GAD1'), cols = c('lightgrey', hue_pal()(9)[9]))
FeaturePlot(jy_all, c('TSHZ1', 'GSX2', 'GAD1'), cols = c('lightgrey', hue_pal()(9)[1]))
FeaturePlot(jy_all, 'DCX', cols = c('lightgrey', 'green')) + NoAxes() + NoLegend()
FeaturePlot(jy_all,  c('LHX6', 'SST'), cols = c('lightgrey', hue_pal()(7)[6]), split.by = 'area', by.col = TRUE)
genes = c('LHX6', 'SST')
plots <- lapply(1:length(genes), function(i){
    plot_features_umap(jy_all, genes[i], pt.size = 0.8, alpha = 1, color = hue_pal()(7)[6])
  })
umaps = plot_grid(plotlist = plots, label_size = 10, nrow = 1)
umaps
DimPlot(jy_all, split.by = 'area', ncol = 3)
FeaturePlot(jy_all, c('TBR1', 'COUPTF2'), cols = c('#F18F01', '#048BA8'), blend= TRUE) 
FeaturePlot(jy_all, c('VIP', 'SST'), cols = c( '#F18F01', '#048BA8'), blend= TRUE) 
FeaturePlot(jy_all, c('TBR1', 'COUPTF2'), cols = c( '#F18F01', '#048BA8'), blend= TRUE) 
DimPlot(jy_all, split.by = 'area', ncol = 3) 
FeaturePlot(jy_all, features = c('GAD1', 'DLX2'), split.by = 'area', by.col = TRUE) 
jy_all

Tree

breakpoints = 1:40/20
i = 1
res_str = 'clust_tree_res.'
norm_str = 'RNA_snn_res.'
for (breakpoint in breakpoints){
  jy_all <- FindClusters(jy_all, resolution = breakpoint, verbose = FALSE)
  jy_all[[paste0(res_str, breakpoint)]] = jy_all[[paste0(norm_str, breakpoint)]]
}
library(clustree)
clustree(jy_all, prefix = res_str)
DotPlot(jy_all, features = all.genes) + RotatedAxis()
DotPlot(jy_all, features = all.genes, group.by = 'broad_areas') + RotatedAxis()
DimPlot(jy_all, split.by = 'broad_areas', ncol = 2)
DimPlot(jy_all, group.by = 'broad_areas') + NoAxes()
XY_164 %>%
  ggplot(aes(x = pixel_1, y = pixel_2)) + geom_point()

LS0tCnRpdGxlOiAic3RfY2x1bXBzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpXcml0dGVuIGJ5IEF1bm95IFBvZGRhcgpKdWx5IDIxc3QsIDIwMjIKCiMgUHJvY2VzcyB0aGUgcHVuY3RhIHF1YW50aWZpZWQgcmF3IGRhdGEKYGBge3IgZXZhbD1GQUxTRX0KY3VycmVudF9maWxlIDwtIHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgKb3V0cHV0X2ZpbGUgPC0gc3RyaW5ncjo6c3RyX3JlcGxhY2UoY3VycmVudF9maWxlLCAnLlJtZCcsICcuUicpCmtuaXRyOjpwdXJsKGN1cnJlbnRfZmlsZSwgb3V0cHV0ID0gb3V0cHV0X2ZpbGUpCmZpbGUuZWRpdChvdXRwdXRfZmlsZSkKYGBgCgojIyBJbXBvcnQgcGFja2FnZXMgYW5kIGZ1bmN0aW9ucwpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkodGljdG9jKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KHBuZykKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KG1hZ2ljaykKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkocGFja2NpcmNsZXMpCmxpYnJhcnkoZ2dhbHQpCmBgYAoKIyMgTG9hZCB0aGUgZGF0YQpgYGB7cn0KZGF0YV9kaXIgPSAnL2hvbWUvYXVub3kvc3QvYXJjX3Byb2ZpbGluZy9zdF9hbmFseXNpcy9oYW5kX2Fubm90YXRlZF9kYXRhL3JldGhyZXNob2xkZWQnCmNsdW1wX2RpciA9ICcvaG9tZS9hdW5veS9zdC9hcmNfcHJvZmlsaW5nL3N0X2FuYWx5c2lzL2hhbmRfYW5ub3RhdGVkX2RhdGEvY2x1bXBzJwptZXRhX2RpciA9ICcvaG9tZS9hdW5veS9zdC9hcmNfcHJvZmlsaW5nL3N0X2FuYWx5c2lzL2hhbmRfYW5ub3RhdGVkX2RhdGEvb3ZlcmxheScKb3V0cHV0X2Rpcl9wbG90ID0gJy9ob21lL2F1bm95L3N0L2FyY19wcm9maWxpbmcvc3RfYW5hbHlzaXMvcmVzdWx0cy9wbG90cycKb3V0cHV0X2Rpcl90YmxzID0gJy9ob21lL2F1bm95L3N0L2FyY19wcm9maWxpbmcvc3RfYW5hbHlzaXMvcmVzdWx0cy90YWJsZXMnCmBgYAoKIyMjIHBsb3QgdGhlIG1ldGEgZGF0YSByZWFsIHF1aWNrCmBgYHtyfQptZXRhX250cnNjdHMgPSByZWFkLmNzdihmaWxlLnBhdGgoY2x1bXBfZGlyLCAnbWV0YScsICdNRVRBX250cnNjdC5jc3YnKSwgaGVhZGVyID0gRkFMU0UpICU+JQogIGFzX3RpYmJsZSgpCmBgYAoKCiMjIyBNZXJnZSBib3RoIGRhdGFzZXRzIGFuZCBnZW5lcmF0ZSBhIG1ldGFkYXRhIGNvbHVtbiB0aGF0IGNvcnJlc3BvbmRzCiMjIyB0byB0aGUgY2VsbCAjCmBgYHtyfQpkZl80MDggPSBkYXRhLmZyYW1lKCkKZm9yIChmaWxlX25hbWUgaW4gbGlzdC5maWxlcyhkYXRhX2RpcikpewogIGlmKGdyZXBsKCcxNjQnLCBmaWxlX25hbWUpKXsKICAgIG5leHQKICB9CiAgI2lmKGdyZXBsKCc0MDhfVEMnLCBmaWxlX25hbWUpIHwgZ3JlcGwoJzQwOF92TVMnLCBmaWxlX25hbWUpKXsKICAjICBuZXh0CiAgI30KICBwcmludChmaWxlX25hbWUpCgogIGRmX3RvX2FwcGVuZCA8LSByZWFkLnRhYmxlKGZpbGUucGF0aChkYXRhX2RpciwgZmlsZV9uYW1lKSwgc2VwID0gJywnLCBoZWFkZXIgPSBUUlVFKQogIAogIHByaW50KGRmX3RvX2FwcGVuZCkKCiAgd2hpbGUobGVuZ3RoKGluZCA8LSB3aGljaChkZl90b19hcHBlbmQkSW1hZ2UuTmFtZSA9PSAiIikpID4gMCl7CiAgICBkZl90b19hcHBlbmQkSW1hZ2UuTmFtZVtpbmRdIDwtIGRmX3RvX2FwcGVuZCRJbWFnZS5OYW1lW2luZCAtMV0KICB9CiAgCiAgY29sbmFtZXMoZGZfdG9fYXBwZW5kKSA8LSB0b3VwcGVyKGNvbG5hbWVzKGRmX3RvX2FwcGVuZCkpCiAgZGZfdG9fYXBwZW5kIDwtIGRmX3RvX2FwcGVuZCAlPiUKICAgIG11dGF0ZShhcmVhID0gc3Ryc3BsaXQoZmlsZV9uYW1lLCAnLmNzdicpW1sxXV0pCiAgCiAgIyMgQWRkIHJlbGF0aXZlX1hZX3Bvc2l0aW9uCiAgCiAgaWYoIWlzX2VtcHR5KGRmXzQwOCkpewogICAgZGZfdG9fYXBwZW5kIDwtIGRmX3RvX2FwcGVuZCAlPiUKICAgICAgICAgIGRwbHlyOjpzZWxlY3QoY29sbmFtZXMoZGZfNDA4KSkKICB9CiAgZGZfNDA4IDwtIHJiaW5kKGRmXzQwOCwgZGZfdG9fYXBwZW5kKQp9CmBgYApgYGB7cn0KZGZfNDA4JElNQUdFLk5BTUUgPSB1bmxpc3QobGFwcGx5KGRmXzQwOCRJTUFHRS5OQU1FLCBnc3ViLCBwYXR0ZXJuPSdfQ2x1c3RlcicsIHJlcGxhY2VtZW50PScnKSkKZGZfNDA4JElNQUdFLk5BTUUgPSB1bmxpc3QobGFwcGx5KGRmXzQwOCRJTUFHRS5OQU1FLCBnc3ViLCBwYXR0ZXJuPSdbKl0nLCByZXBsYWNlbWVudD0nJykpCmRmXzQwOCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl80MDgkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nWCcsIHJlcGxhY2VtZW50PScnKSkKZGZfNDA4JElNQUdFLk5BTUUgPSB1bmxpc3QobGFwcGx5KGRmXzQwOCRJTUFHRS5OQU1FLCBnc3ViLCBwYXR0ZXJuPSdMMl8nLCByZXBsYWNlbWVudD0nTDItJykpCmRmXzQwOCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl80MDgkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nLUwyJywgcmVwbGFjZW1lbnQ9J19MMicpKQpkZl80MDgkSU1BR0UuTkFNRSA9IHVubGlzdChsYXBwbHkoZGZfNDA4JElNQUdFLk5BTUUsIGdzdWIsIHBhdHRlcm49J1RjXzEyJywgcmVwbGFjZW1lbnQ9J1RDXzEyJykpCiMjIE1pc3NpbmcKZGZfNDA4ID0gZGZfNDA4W2RmXzQwOCRJTUFHRS5OQU1FICE9ICdMYXllcjEnLCBdCmRmXzQwOCA9IGRmXzQwOFtkZl80MDgkSU1BR0UuTkFNRSAhPSAnVENfMScsIF0KZGZfNDA4ID0gZGZfNDA4W2RmXzQwOCRJTUFHRS5OQU1FICE9ICdUQ18xOCcsIF0KZGZfNDA4ID0gZGZfNDA4W2RmXzQwOCRJTUFHRS5OQU1FICE9ICdUQ18xOScsIF0KI2RmXzQwOCRJTUFHRS5OQU1FID0gdG91cHBlcihkZl80MDgkSU1BR0UuTkFNRSkKdW5pcXVlKGRmXzQwOCRJTUFHRS5OQU1FKQpgYGAKCgojIyBPcmRlciB0aGUgaW1hZ2VzCmBgYHtyfQp1bmlxdWUoZGZfNDA4JElNQUdFLk5BTUUpCmltYWdlc19vcmRlcmVkID0gYygnVENfMjAnLCAnVENfMTcnLCAnVENfMTYnLCAnVENfMTUnLCAnVENfMTQnLCAnVENfMTMnLCAnVENfMTInLCAnVENfMTEnLCAnVENfMTAnLCAnVENfOScsICdUQ184JywgJ1RDXzcnLCAnVENfNicsICdUQ181JywKICAgICAgICAgICAgICAgICAgICdUQ180JywgJ1RDXzMnLCAnVENfMicsICdDQ180JywgJ0NDXzUnLCAnQ0NfNicsICdDQ183JywgJ0NDXzgnLCAnQ0NfOScsICdDQ18xMCcsICdDQ18xMScsICdDQ18xMicsICdDQ19MMi0zJywgJ0NDX0wyLTInLCAnQ0NfTDItMScsICdDQ19Db3J0aWNhbDEnLCAnQ0NfQ29ydGljYWwyJykKYGBgCgpgYGB7cn0KeF9ob3J6ID0gMTpsZW5ndGgoaW1hZ2VzX29yZGVyZWQpICogMzUKeV9ob3J6ID0gcmVwKDAsIGxlbmd0aChpbWFnZXNfb3JkZXJlZCkpCmhvcnpfZW1iZWRkaW5nID0gZGF0YS5mcmFtZSgpCmRmXzQwOCRYX2hvcnogPSAtMQpkZl80MDgkWV9ob3J6ID0gLTEKZGZfNDA4JGNsdW1wID0gTmFOCmNsdW1wX2hlYWRlciA9ICc0MDhfJwpjbHVtcF9maWxlcyA9IGxpc3QuZmlsZXMoY2x1bXBfZGlyKQpJTUFHRV9TSVpFID0gMTAyNAojIyBUaGlzIGlzIHRoZSBzaXplIG9mIGFuIGltYWdlIGluIHRoZSBnbG9iYWwgY29vcmRpbmF0ZSBzcGFjZQpJTUFHRV9MRU4gPSAyNQoKaW1hZ2VzID0gbGlzdC5maWxlcyhtZXRhX2RpcikKZm9yKGkgaW4gMTpsZW5ndGgoaW1hZ2VzX29yZGVyZWQpKXsKICAgIGltYWdlX25hbWUgPSBpbWFnZXNfb3JkZXJlZFtpXQogICAgcHJpbnQoaW1hZ2VfbmFtZSkKICAgIHNwbGl0X25hbWVzID0gc3Ryc3BsaXQoaW1hZ2VfbmFtZSwgJ18nKQogICAgY29ydGV4ID0gdG91cHBlcihzcGxpdF9uYW1lc1tbMV1dWzFdKQogICAgbnVtYmVyID0gc3BsaXRfbmFtZXNbWzFdXVsyXQogICAgbnVtYmVyX2NzdiA9IHBhc3RlMCgnXycsIG51bWJlciwgJy5jc3YnKQogICAgZmlsZW5hbWUgPSBpbWFnZXNbZ3JlcGwoY29ydGV4LCBpbWFnZXMpICYgZ3JlcGwobnVtYmVyX2NzdiwgaW1hZ2VzKSAmIGdyZXBsKCc0MDgnLCBpbWFnZXMpXQogICAgY29vcmRpbmF0ZXMgPSByZWFkLnRhYmxlKGZpbGUucGF0aChtZXRhX2RpciwgZmlsZW5hbWUpLCBzZXAgPSAnLCcsIGhlYWRlciA9IFRSVUUpCiAgICAjIyBjaGVja2VkIGFscmVhZHkgdGhhdCBsaXN0cyBhcmUgZXF1YWwsIG1pc3NpbmcgMSwgMTgsIDE5IGZvciBub3csIGxheWVyIDEgYW5kIG90aGVycwogICAgCiAgICAjIyBHZXQgdGhlIGNsdW1wcwogICAgZmlsZW5hbWUgPSBjbHVtcF9maWxlc1tncmVwbChjbHVtcF9oZWFkZXIsIGNsdW1wX2ZpbGVzKSAmIGdyZXBsKHBhc3RlMChpbWFnZV9uYW1lLCAnXycpLGNsdW1wX2ZpbGVzKV0KICAgIGNsdW1wX2RmID0gYXMuZGF0YS5mcmFtZSh0KHJlYWQuY3N2KGZpbGUucGF0aChjbHVtcF9kaXIsIGZpbGVuYW1lKSwgaGVhZGVyID0gRkFMU0UpKSkKICAgIGNvbG5hbWVzKGNsdW1wX2RmKSA9IGMoJ3JvaScsICdjbHVzdGVyJykKICAgIGNsdW1wX2RmJHJvaSA9IGFzLm51bWVyaWMoY2x1bXBfZGYkcm9pKQogICAgCiAgICBpbWFnZV9pZHhzID0gd2hpY2goZGZfNDA4JElNQUdFLk5BTUUgPT0gaW1hZ2VfbmFtZSkKICAgIGNsdW1wX2RmJHJvaV9pZHhzID0gaW1hZ2VfaWR4c1sxXSArIGNsdW1wX2RmJHJvaSAtIDEKICAgIGRmXzQwOFtjbHVtcF9kZiRyb2lfaWR4cywgImNsdW1wIl0gPSBwYXN0ZTAoY2x1bXBfaGVhZGVyLCBpbWFnZV9uYW1lLCAnXycsIGNsdW1wX2RmJGNsdXN0ZXIpCiAgICAKICAgICMjIHNvIHRoaXMgaXMgYSBsaXR0bGUgdHJpY2t5LCBzbyBuZWVkIHRvIGdldCBpdCByaWdodAogICAgIyMgUmVtZW1iZXIsIGl0IGlzIHRoZSB0b3AgcmlnaHQgdGhhdCB0aGUgY29vcmRpbmF0ZSBpcyBjb21pbmcgZnJvbSwgYnV0CiAgICAjIyB0aGUgYm90dG9tIHJpZ2h0IGlzIHRoZSBuZXcgY29vcmRpbmF0ZSBzcGFjZS4KICAgICMjIHNvIGZpcnN0IHdoZW4gd2UgZ2V0IHRoZSBvcmlnaW5hbCBjb29yZGluYXRlIHNwYWNlLCB0byBzZXQgdG8gcmVsYXRpdmUKICAgICMjIG9mIGJvdHRvbSB3b3VsZCBiZSB0aGUgc2FtZSBYLCBidXQgMTAyNCAtIFkKICAgIAogICAgIyMgcHVzaCBvdXQgdGhlIGNvb3JkaW5hdGVzIGZvciBiZXR0ZXIgdmlzdWFsaXphdGlvbgogICAgI3hfcmVwZWxsZWQgPC0gKDUxMiAtIGNvb3JkaW5hdGVzJFhfQ29vcmRpbmF0ZV9Jbl9waXhlbHMpCiAgICAKICAgIAogICAgZGZfNDA4W2RmXzQwOCRJTUFHRS5OQU1FID09IGltYWdlX25hbWUsICdYX2hvcnonXSA9IChjb29yZGluYXRlcyRYX0Nvb3JkaW5hdGVfSW5fcGl4ZWxzIC8gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElNQUdFX1NJWkUgKiBJTUFHRV9MRU4pICsgeV9ob3J6W2ldCiAgICBkZl80MDhbZGZfNDA4JElNQUdFLk5BTUUgPT0gaW1hZ2VfbmFtZSwgJ1lfaG9yeiddID0gKCgxMDI0LWNvb3JkaW5hdGVzJFlfQ29vcmRpbmF0ZV9Jbl9waXhlbHMpIC8gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElNQUdFX1NJWkUgKiBJTUFHRV9MRU4pICsgeF9ob3J6W2ldCn0KYGBgCgojIyBXZSBoYXZlIHRoZSBjb29yZGluYXRlcyBmb3IgNDA4X1RDIGFuZCBvdGhlcnMKYGBge3J9CnJvd25hbWVzKGRmXzQwOCkgPSAxOm5yb3coZGZfNDA4KQpqeV80MDggPSBkZl80MDggJT4lCiAgZHBseXI6OnNlbGVjdCgtYyhhcmVhLCBJTUFHRS5OQU1FLCBYX2hvcnosIFlfaG9yeiwgY2x1bXApKSAlPiUKICB0KCkgJT4lCiAgQ3JlYXRlU2V1cmF0T2JqZWN0KCkKYGBgCgojIyBqdXN0IHNldCBldmVyeXRoaW5nIGZyb20gYmVsb3cgMSBpbiByYXRpbyB0byB6ZXJvCmBgYHtyfQpqeV80MDggPC0gTm9ybWFsaXplRGF0YShqeV80MDgsIHNjYWxlLmZhY3RvciA9IDFlNSkgIyMjCm5vcm1lZCA9IEdldEFzc2F5RGF0YShqeV80MDgsIHNsb3QgPSAnZGF0YScpCm5vcm1lZFtub3JtZWQgPCAzXSA9IDAKZm9yKGdlbmVfbmFtZSBpbiByb3duYW1lcyhqeV80MDgpKSB7CiAgbWRuX2dlbmVfZXhwciA9IG1lZGlhbihub3JtZWRbZ2VuZV9uYW1lLCBub3JtZWRbZ2VuZV9uYW1lLCBdID4gMF0pCiAgI3ByaW50KGdlbmVfbmFtZSkKICAjcHJpbnQobWRuX2dlbmVfZXhwcikKICAjbm9ybWVkW2dlbmVfbmFtZSwgbm9ybWVkW2dlbmVfbmFtZSwgXSA8IG1kbl9nZW5lX2V4cHJdID0gMAp9Cmp5XzQwOCA8LSBTZXRBc3NheURhdGEoanlfNDA4LCBzbG90ID0gJ2RhdGEnLCBub3JtZWQpCmBgYAoKYGBge3J9Cmp5XzQwOCA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhqeV80MDgsIHNlbGVjdGlvbi5tZXRob2QgPSAidnN0IikKYWxsLmdlbmVzIDwtIHJvd25hbWVzKGp5XzQwOCkKanlfNDA4IDwtIFNjYWxlRGF0YShqeV80MDgsIGZlYXR1cmVzID0gYWxsLmdlbmVzKQpqeV80MDggPC0gUnVuUENBKGp5XzQwOCwgYXBwcm94ID0gRkFMU0UpCmp5XzQwOCA8LSBGaW5kTmVpZ2hib3JzKGp5XzQwOCwgZGltcyA9IDE6MzApCmp5XzQwOCA8LSBGaW5kQ2x1c3RlcnMoanlfNDA4LCByZXNvbHV0aW9uID0gMS41KQpqeV80MDggPC0gUnVuVU1BUChqeV80MDgsIGRpbXMgPSAxOjMwKQoKRGltUGxvdChqeV80MDgsICByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gJ3NldXJhdF9jbHVzdGVycycpICsgTm9BeGVzKCkKYGBgCgpgYGB7cn0KanlfNDA4JGNsdW1wID0gZGZfNDA4JGNsdW1wCgpEaW1QbG90KGp5XzQwOCwgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAnY2x1bXAnKSArIE5vQXhlcygpICsgTm9MZWdlbmQoKQoKYGBgCgoKYGBge3J9Cmhjb29yZHMgPSBkZl80MDggJT4lIGRwbHlyOjpzZWxlY3QoYygnWF9ob3J6JywgJ1lfaG9yeicpKSAlPiUgYXMubWF0cml4KCkKY29sbmFtZXMoaGNvb3JkcykgPC0gYygncGl4ZWxfMScsICdwaXhlbF8yJykKCmp5XzQwOFtbIkgiXV0gPC0gQ3JlYXRlRGltUmVkdWNPYmplY3QoZW1iZWRkaW5ncyA9IGhjb29yZHMsIGtleSA9ICJwaXhlbF8iLCBhc3NheSA9IERlZmF1bHRBc3NheShqeV80MDgpKQpgYGAKCiMjIyBNZXJnZSBib3RoIGRhdGFzZXRzIGFuZCBnZW5lcmF0ZSBhIG1ldGFkYXRhIGNvbHVtbiB0aGF0IGNvcnJlc3BvbmRzCiMjIyB0byB0aGUgY2VsbCAjCmBgYHtyfQpkZl8xNjQgPSBkYXRhLmZyYW1lKCkKZm9yIChmaWxlX25hbWUgaW4gbGlzdC5maWxlcyhkYXRhX2RpcikpewogIHByaW50KGZpbGVfbmFtZSkKICBpZihncmVwbCgnNDA4JywgZmlsZV9uYW1lKSl7CiAgICBuZXh0CiAgfQogICNpZihncmVwbCgnNDA4X1RDJywgZmlsZV9uYW1lKSB8IGdyZXBsKCc0MDhfdk1TJywgZmlsZV9uYW1lKSl7CiAgIyAgbmV4dAogICN9CiAgZGZfdG9fYXBwZW5kIDwtIHJlYWQudGFibGUoZmlsZS5wYXRoKGRhdGFfZGlyLCBmaWxlX25hbWUpLCBzZXAgPSAnLCcsIGhlYWRlciA9IFRSVUUpCiAgd2hpbGUobGVuZ3RoKGluZCA8LSB3aGljaChkZl90b19hcHBlbmQkSW1hZ2UuTmFtZSA9PSAiIikpID4gMCl7CiAgICBkZl90b19hcHBlbmQkSW1hZ2UuTmFtZVtpbmRdIDwtIGRmX3RvX2FwcGVuZCRJbWFnZS5OYW1lW2luZCAtMV0KICB9CiAgCiAgY29sbmFtZXMoZGZfdG9fYXBwZW5kKSA8LSB0b3VwcGVyKGNvbG5hbWVzKGRmX3RvX2FwcGVuZCkpCiAgZGZfdG9fYXBwZW5kIDwtIGRmX3RvX2FwcGVuZCAlPiUKICAgIG11dGF0ZShhcmVhID0gc3Ryc3BsaXQoZmlsZV9uYW1lLCAnLmNzdicpW1sxXV0pCiAgCiAgIyMgQWRkIHJlbGF0aXZlX1hZX3Bvc2l0aW9uCiAgCiAgaWYoIWlzX2VtcHR5KGRmXzE2NCkpewogICAgZGZfdG9fYXBwZW5kIDwtIGRmX3RvX2FwcGVuZCAlPiUKICAgICAgICAgIGRwbHlyOjpzZWxlY3QoY29sbmFtZXMoZGZfMTY0KSkKICB9CiAgZGZfMTY0IDwtIHJiaW5kKGRmXzE2NCwgZGZfdG9fYXBwZW5kKQp9CmBgYApgYGB7cn0KZGZfMTY0JElNQUdFLk5BTUUgPSB1bmxpc3QobGFwcGx5KGRmXzE2NCRJTUFHRS5OQU1FLCBnc3ViLCBwYXR0ZXJuPSdDQy0nLCByZXBsYWNlbWVudD0nQ0NfJykpCmRmXzE2NCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl8xNjQkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nWypdJywgcmVwbGFjZW1lbnQ9JycpKQpkZl8xNjQkSU1BR0UuTkFNRSA9IHVubGlzdChsYXBwbHkoZGZfMTY0JElNQUdFLk5BTUUsIGdzdWIsIHBhdHRlcm49J1gnLCByZXBsYWNlbWVudD0nJykpCmRmXzE2NCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl8xNjQkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nTDInLCByZXBsYWNlbWVudD0nQ0NfTDInKSkKZGZfMTY0JElNQUdFLk5BTUUgPSB1bmxpc3QobGFwcGx5KGRmXzE2NCRJTUFHRS5OQU1FLCBnc3ViLCBwYXR0ZXJuPSdMMl8nLCByZXBsYWNlbWVudD0nTDItJykpCmRmXzE2NCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl8xNjQkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nLUwyJywgcmVwbGFjZW1lbnQ9J19MMicpKQp0Y19jb3J0aWNhbF9uYW1lc19iYWQgPSBkZl8xNjRbZ3JlcGwoJ1RDJywgZGZfMTY0JGFyZWEpICYgZ3JlcGwoJ0NvcnRpY2FsJywgZGZfMTY0JElNQUdFLk5BTUUpLCAnSU1BR0UuTkFNRSddCmRmXzE2NFtncmVwbCgnVEMnLCBkZl8xNjQkYXJlYSkgJiBncmVwbCgnQ29ydGljYWwnLCBkZl8xNjQkSU1BR0UuTkFNRSksICdJTUFHRS5OQU1FJ10gPSB1bmxpc3QobGFwcGx5KHRjX2NvcnRpY2FsX25hbWVzX2JhZCwgZ3N1YiwgcGF0dGVybj0nQ29ydCcsIHJlcGxhY2VtZW50PSdUQ19Db3J0JykpCmNjX2NvcnRpY2FsX25hbWVzX2JhZCA9IGRmXzE2NFtncmVwbCgnQ0MnLCBkZl8xNjQkYXJlYSkgJiBncmVwbCgnQ29ydGljYWwnLCBkZl8xNjQkSU1BR0UuTkFNRSksICdJTUFHRS5OQU1FJ10KZGZfMTY0W2dyZXBsKCdDQycsIGRmXzE2NCRhcmVhKSAmIGdyZXBsKCdDb3J0aWNhbCcsIGRmXzE2NCRJTUFHRS5OQU1FKSwgJ0lNQUdFLk5BTUUnXSA9IHVubGlzdChsYXBwbHkoY2NfY29ydGljYWxfbmFtZXNfYmFkLCBnc3ViLCBwYXR0ZXJuPSdDb3J0JywgcmVwbGFjZW1lbnQ9J0NDX0NvcnQnKSkKI2RmXzE2NCRJTUFHRS5OQU1FID0gdW5saXN0KGxhcHBseShkZl8xNjQkSU1BR0UuTkFNRSwgZ3N1YiwgcGF0dGVybj0nVGNfMTInLCByZXBsYWNlbWVudD0nVENfMTInKSkKIyMgTWlzc2luZwojZGZfMTY0ID0gZGZfMTY0W2RmXzE2NCRJTUFHRS5OQU1FICE9ICdMYXllcjEnLCBdCiNkZl8xNjQgPSBkZl8xNjRbZGZfMTY0JElNQUdFLk5BTUUgIT0gJ1RDXzEnLCBdCiNkZl8xNjQgPSBkZl8xNjRbZGZfMTY0JElNQUdFLk5BTUUgIT0gJ1RDXzE4JywgXQojZGZfMTY0ID0gZGZfMTY0W2RmXzE2NCRJTUFHRS5OQU1FICE9ICdUQ18xOScsIF0KI2RmXzE2NCRJTUFHRS5OQU1FID0gdG91cHBlcihkZl8xNjQkSU1BR0UuTkFNRSkKdW5pcXVlKGRmXzE2NCRJTUFHRS5OQU1FKQpgYGAKIyMgTm93IHdlIGtub3cgdGhhdCBldmVyeXRoaW5nIGlzIGVxdWFsIHRvIG9uZSBhbm90aGVyLCB3ZSBzaG91bGQgbG9hZCB0aGUgdmFyaWFibGUKCmBgYHtyfQppbWFnZV9uYW1lcyA9IHVuaXF1ZShkZl8xNjQkSU1BR0UuTkFNRSkKIyBQcmVzZXQgdGhlc2UgdmFyaWFibGVzIHRvIG5lZ2F0aXZlIHZhbHVlcyBzbyBJIGNhbiBlYXNpbHkgY2hlY2sgaWYgdGhleSB3ZXJlIHVwZGF0ZWQgbGF0ZXIKZGZfMTY0JFggPSAtMQpkZl8xNjQkWSA9IC0xCmRmXzE2NCRjbHVtcCA9IE5hTgpjbHVtcF9oZWFkZXIgPSAnMTY0XycKY2x1bXBfZmlsZXMgPSBsaXN0LmZpbGVzKGNsdW1wX2RpcikKIyBzZXQgc29tZSBub3JtYWxpemF0aW9uIHZhcmlhYmxlcwojIyBUaGlzIGlzIHRoZSBzaXplIG9mIHRoZSBpbWFnZSB3aGVuIHRoZSBwaXhlbCB2YWx1ZXMgYXJlIHRha2VuIGZyb20gdG9wIGxlZnQgZG93bgpJTUFHRV9TSVpFID0gMTAyNAojIyBUaGlzIGlzIHRoZSBzaXplIG9mIGFuIGltYWdlIGluIHRoZSBnbG9iYWwgY29vcmRpbmF0ZSBzcGFjZQpJTUFHRV9MRU4gPSAzMgpUQ19JTUFHRV9IRUlHSFQgPSA0MTAKVENfSU1BR0VfV0lEVEggPSA0NDYKQ0NfSU1BR0VfSEVJR0hUPSA0MjIKQ0NfSU1BR0VfV0lEVEggPSAyMTQKCiMgTG9hZCB0aGUgZGF0YWZyYW1lIHdpdGggZ2xvYmFsIGFuZCByZWxhdGl2ZSBjb29yZGluYXRlcwppbWdfY29yZHMgPSByZWFkLnRhYmxlKGZpbGUucGF0aChtZXRhX2RpciwgJzE2NF9waXhlbF9jb29yZGluYXRlcy5jc3YnKSwgc2VwID0gJywnLCBoZWFkZXIgPSBUUlVFKQoKaW1hZ2VzID0gbGlzdC5maWxlcyhtZXRhX2RpcikKZm9yKGltYWdlX25hbWUgaW4gaW1hZ2VfbmFtZXMpewogICAgaWYoZ3JlcGwoJzQwOCcsIGltYWdlX25hbWUpKXsKICAgICAgbmV4dAogICAgfQogICAgcHJpbnQoaW1hZ2VfbmFtZSkKICAgIHNwbGl0X25hbWVzID0gc3Ryc3BsaXQoaW1hZ2VfbmFtZSwgJ18nKQogICAgY29ydGV4ID0gdG91cHBlcihzcGxpdF9uYW1lc1tbMV1dWzFdKQogICAgbnVtYmVyID0gc3BsaXRfbmFtZXNbWzFdXVsyXQogICAgbnVtYmVyX2NzdiA9IHBhc3RlMCgnXycsIG51bWJlciwgJy5jc3YnKQogICAgZmlsZW5hbWUgPSBpbWFnZXNbZ3JlcGwoY29ydGV4LCBpbWFnZXMpICYgZ3JlcGwobnVtYmVyX2NzdiwgaW1hZ2VzKSAmIGdyZXBsKCcxNjQnLCBpbWFnZXMpXQogICAgY29vcmRpbmF0ZXMgPSByZWFkLnRhYmxlKGZpbGUucGF0aChtZXRhX2RpciwgZmlsZW5hbWUpLCBzZXAgPSAnLCcsIGhlYWRlciA9IFRSVUUpCiAgICAKICAgICMjIEdldCB0aGUgY2x1bXBzCiAgICBmaWxlbmFtZSA9IGNsdW1wX2ZpbGVzW2dyZXBsKGNsdW1wX2hlYWRlciwgY2x1bXBfZmlsZXMpICYgZ3JlcGwocGFzdGUwKGltYWdlX25hbWUsICdfJyksY2x1bXBfZmlsZXMpXQogICAgaWYobGVuZ3RoKGZpbGVuYW1lICE9IDApKXsKICAgICAgY2x1bXBfZGYgPSBhcy5kYXRhLmZyYW1lKHQocmVhZC5jc3YoZmlsZS5wYXRoKGNsdW1wX2RpciwgZmlsZW5hbWUpLCBoZWFkZXIgPSBGQUxTRSkpKQogICAgICBjb2xuYW1lcyhjbHVtcF9kZikgPSBjKCdyb2knLCAnY2x1c3RlcicpCiAgICAgIGNsdW1wX2RmJHJvaSA9IGFzLm51bWVyaWMoY2x1bXBfZGYkcm9pKQogICAgICAKICAgICAgaWYoaW1hZ2VfbmFtZSA9PSAiQ0NfTDItMSIpewogICAgICAgIGNvb3JkaW5hdGVzID0gY29vcmRpbmF0ZXNbYygxOjM3LCAzOTpucm93KGNvb3JkaW5hdGVzKSksIF0KICAgICAgICBpZigzOCAlaW4lIGNsdW1wX2RmJHJvaSl7Y2x1bXBfZGYgPSBjbHVtcF9kZltjbHVtcF9kZiRyb2kgIT0gMzgsIF19CiAgICAgICAgY2x1bXBfZGYkcm9pW2NsdW1wX2RmJHJvaSA+IDM3XSA9IGNsdW1wX2RmJHJvaVtjbHVtcF9kZiRyb2kgPiAzN10gLSAxCiAgICAgIH0KICAgICAgCiAgICAgIGltYWdlX2lkeHMgPSB3aGljaChkZl8xNjQkSU1BR0UuTkFNRSA9PSBpbWFnZV9uYW1lKQogICAgICBjbHVtcF9kZiRyb2lfaWR4cyA9IGltYWdlX2lkeHNbMV0gKyBjbHVtcF9kZiRyb2kgLSAxCiAgICAgIGRmXzE2NFtjbHVtcF9kZiRyb2lfaWR4cywgImNsdW1wIl0gPSBwYXN0ZTAoY2x1bXBfaGVhZGVyLCBpbWFnZV9uYW1lLCAnXycsIGNsdW1wX2RmJGNsdXN0ZXIpCiAgICB9IGVsc2V7CiAgICAgIHByaW50KCdObyBjbHVtcHMhJykKICAgICAgcHJpbnQoaW1hZ2VfbmFtZSkKICAgIH0KICAgIAogICAgaWYoY29ydGV4ID09ICdDQycpeyAKICAgICAgcHJpbnQocGFzdGUoJ2NjJywgZmlsZW5hbWUsIGltYWdlX25hbWUpKQogICAgICAjIyBTbyBpZiBDQywgd2UgYWRkIHRoZSBjb29yZGluYXRlcyBmb3IgVENfMSB0byBvdmVyYWxsIGltYWdlIGNvb3JkaW5hdGVzCiAgICAgIHhfYWRqID0gaW1nX2NvcmRzW2ltZ19jb3JkcyROYW1lID09ICdUQ18xJywgJ3gnXSArIAogICAgICAgICAgICAgICAgaW1nX2NvcmRzW2ltZ19jb3JkcyROYW1lID09ICdHX0NDMV90b19UQzEnLCAneCddCiAgICAgICMjIFN0YXJ0IGZyb20gYm90dG9tLCBhZGQgdGhlIGhlaWdodCwgc3VidHJhY3QgVENfMSBoZWlnaHQsIGFuZCB0aGVuIGdsb2JhbCBDQzEgdG8gVEMxCiAgICAgIHlfYWRqID0gVENfSU1BR0VfSEVJR0hUIC0gaW1nX2NvcmRzW2ltZ19jb3JkcyROYW1lID09ICdUQ18xJywgJ3knXSArCiAgICAgICAgICAgICAgICBpbWdfY29yZHNbaW1nX2NvcmRzJE5hbWUgPT0gJ0dfQ0MxX3RvX1RDMScsICd5J10gKyBDQ19JTUFHRV9IRUlHSFQKICAgIH1lbHNlewogICAgICBwcmludChwYXN0ZSgndGMnLCBmaWxlbmFtZSwgaW1hZ2VfbmFtZSkpCiAgICAgIHhfYWRqID0gMAogICAgICB5X2FkaiA9IFRDX0lNQUdFX0hFSUdIVAogICAgfQogICAgCiAgICAjIyBTbyBkb24ndCBkbyByZXBlbGxlZCBmb3Igbm93CiAgICAjeF9yZXBlbGxlZCA8LSAoNTEyIC0gY29vcmRpbmF0ZXMkWF9Db29yZGluYXRlX0luX3BpeGVscykKICAgIAogICAgIyMgc28gdGhlIHJlc2l6ZWQgeCBkaXN0YW5jZSBpcyBmcm9tIGxlZnQsIHNvIGp1c3QgYWRkIHRvIHRoZSBib3ggbG9jYXRpb24gYW5kIGFkagogICAgZGZfMTY0W2RmXzE2NCRJTUFHRS5OQU1FID09IGltYWdlX25hbWUsICdYJ10gPSAoY29vcmRpbmF0ZXMkWF9Db29yZGluYXRlX0luX3BpeGVscyAvIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTUFHRV9TSVpFICogSU1BR0VfTEVOKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1nX2NvcmRzW2ltZ19jb3JkcyROYW1lID09IGltYWdlX25hbWUsICd4J10gKyB4X2FkagogICAgIyMgcmVzaXplZCB5IGRpc3RhbmNlCiAgICBkZl8xNjRbZGZfMTY0JElNQUdFLk5BTUUgPT0gaW1hZ2VfbmFtZSwgJ1knXSA9IHlfYWRqIC0gaW1nX2NvcmRzW2ltZ19jb3JkcyROYW1lID09IGltYWdlX25hbWUsICd5J10gLSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGNvb3JkaW5hdGVzJFlfQ29vcmRpbmF0ZV9Jbl9waXhlbHMgLyBJTUFHRV9TSVpFICogCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElNQUdFX0xFTikgIAp9CmBgYAoKCgojIyBXZSBoYXZlIHRoZSBjb29yZGluYXRlcyBmb3IgNDA4X1RDIGFuZCBvdGhlcnMKYGBge3J9CnJvd25hbWVzKGRmXzE2NCkgPSAxOm5yb3coZGZfMTY0KQpqeV8xNjQgPSBkZl8xNjQgJT4lCiAgZHBseXI6OnNlbGVjdCgtYyhhcmVhLCBJTUFHRS5OQU1FLCBYLCBZLCBjbHVtcCkpICU+JQogIHQoKSAlPiUKICBDcmVhdGVTZXVyYXRPYmplY3QoKQpgYGAKCiMjIGp1c3Qgc2V0IGV2ZXJ5dGhpbmcgZnJvbSBiZWxvdyAxIGluIHJhdGlvIHRvIHplcm8KYGBge3J9Cmp5XzE2NCA8LSBOb3JtYWxpemVEYXRhKGp5XzE2NCwgc2NhbGUuZmFjdG9yID0gMWU1KSAjIyMKbm9ybWVkID0gR2V0QXNzYXlEYXRhKGp5XzE2NCwgc2xvdCA9ICdkYXRhJykKbm9ybWVkW25vcm1lZCA8IDNdID0gMApmb3IoZ2VuZV9uYW1lIGluIHJvd25hbWVzKGp5XzE2NCkpIHsKICBtZG5fZ2VuZV9leHByID0gbWVkaWFuKG5vcm1lZFtnZW5lX25hbWUsIG5vcm1lZFtnZW5lX25hbWUsIF0gPiAwXSkKICAjcHJpbnQoZ2VuZV9uYW1lKQogICNwcmludChtZG5fZ2VuZV9leHByKQogICNub3JtZWRbZ2VuZV9uYW1lLCBub3JtZWRbZ2VuZV9uYW1lLCBdIDwgbWRuX2dlbmVfZXhwcl0gPSAwCn0KanlfMTY0IDwtIFNldEFzc2F5RGF0YShqeV8xNjQsIHNsb3QgPSAnZGF0YScsIG5vcm1lZCkKYGBgCgpgYGB7cn0KanlfMTY0IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGp5XzE2NCwgc2VsZWN0aW9uLm1ldGhvZCA9ICJ2c3QiKQphbGwuZ2VuZXMgPC0gcm93bmFtZXMoanlfMTY0KQpqeV8xNjQgPC0gU2NhbGVEYXRhKGp5XzE2NCwgZmVhdHVyZXMgPSBhbGwuZ2VuZXMpCmp5XzE2NCA8LSBSdW5QQ0EoanlfMTY0LCBhcHByb3ggPSBGQUxTRSkKanlfMTY0IDwtIEZpbmROZWlnaGJvcnMoanlfMTY0LCBkaW1zID0gMTozMCkKanlfMTY0IDwtIEZpbmRDbHVzdGVycyhqeV8xNjQsIHJlc29sdXRpb24gPSAwLjgpCmp5XzE2NCA8LSBSdW5VTUFQKGp5XzE2NCwgZGltcyA9IDE6MzApCmp5XzE2NCRjbHVtcCA8LSBkZl8xNjQkY2x1bXAKCkRpbVBsb3QoanlfMTY0LCAgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICdzZXVyYXRfY2x1c3RlcnMnKSArIE5vQXhlcygpCmBgYApgYGB7cn0KRGltUGxvdChqeV8xNjQsICByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gJ2NsdW1wJykgKyBOb0F4ZXMoKSArIE5vTGVnZW5kKCkKYGBgCgpgYGB7cn0KdW5pcXVlKGRmXzE2NCRJTUFHRS5OQU1FKQppbWFnZXNfb3JkZXJlZCA9IGMoJ1RDX0NvcnRpY2FsMycsICdUQ19Db3J0aWNhbDInLCAnVENfQ29ydGljYWwxJywgJ1RDXzEwJywgJ1RDXzknLCAnVENfOCcsICdUQ183JywgJ1RDXzYnLCAnVENfNScsICdUQ180JywgJ1RDXzMnLCAnVENfMicsJ1RDXzEnLCdDQ18yJywnQ0NfMycsCiAgICAgICAgICAgJ0NDXzQnLCAnQ0NfNScsICdDQ182JywgJ0NDXzcnLCAnQ0NfOCcsICdDQ185JywgJ0NDXzEwJywKICAgICAgICAgICAnQ0NfTDItMScsICdDQ19MMi0yJywgJ0NDX0wyLTMnLCAnQ0NfQ29ydGljYWwxJywgJ0NDX0NvcnRpY2FsMicpCmBgYAoKYGBge3J9CnhfaG9yeiA9IDE6bGVuZ3RoKGltYWdlc19vcmRlcmVkKSAqIDM1CnlfaG9yeiA9IHJlcCgwLCBsZW5ndGgoaW1hZ2VzX29yZGVyZWQpKQpob3J6X2VtYmVkZGluZyA9IGRhdGEuZnJhbWUoKQpkZl8xNjQkWF9ob3J6ID0gLTEKZGZfMTY0JFlfaG9yeiA9IC0xCgppbWFnZXMgPSBsaXN0LmZpbGVzKG1ldGFfZGlyKQpmb3IoaSBpbiAxOmxlbmd0aChpbWFnZXNfb3JkZXJlZCkpewogICAgaW1hZ2VfbmFtZSA9IGltYWdlc19vcmRlcmVkW2ldCiAgICBwcmludChpbWFnZV9uYW1lKQogICAgc3BsaXRfbmFtZXMgPSBzdHJzcGxpdChpbWFnZV9uYW1lLCAnXycpCiAgICBjb3J0ZXggPSB0b3VwcGVyKHNwbGl0X25hbWVzW1sxXV1bMV0pCiAgICBudW1iZXIgPSBzcGxpdF9uYW1lc1tbMV1dWzJdCiAgICBudW1iZXJfY3N2ID0gcGFzdGUwKCdfJywgbnVtYmVyLCAnLmNzdicpCiAgICBmaWxlbmFtZSA9IGltYWdlc1tncmVwbChjb3J0ZXgsIGltYWdlcykgJiBncmVwbChudW1iZXJfY3N2LCBpbWFnZXMpICYgZ3JlcGwoJzE2NCcsIGltYWdlcyldCiAgICBjb29yZGluYXRlcyA9IHJlYWQudGFibGUoZmlsZS5wYXRoKG1ldGFfZGlyLCBmaWxlbmFtZSksIHNlcCA9ICcsJywgaGVhZGVyID0gVFJVRSkKICAgIGlmKGltYWdlX25hbWUgPT0gIkNDX0wyLTEiKXsKICAgICAgY29vcmRpbmF0ZXMgPSBjb29yZGluYXRlc1tjKDE6MzcsIDM5Om5yb3coY29vcmRpbmF0ZXMpKSwgXQogICAgfQogICAgIyMgY2hlY2tlZCBhbHJlYWR5IHRoYXQgbGlzdHMgYXJlIGVxdWFsLCBtaXNzaW5nIDEsIDE4LCAxOSBmb3Igbm93LCBsYXllciAxIGFuZCBvdGhlcnMKIAogICAgIyMgc28gdGhpcyBpcyBhIGxpdHRsZSB0cmlja3ksIHNvIG5lZWQgdG8gZ2V0IGl0IHJpZ2h0CiAgICAjIyBSZW1lbWJlciwgaXQgaXMgdGhlIHRvcCByaWdodCB0aGF0IHRoZSBjb29yZGluYXRlIGlzIGNvbWluZyBmcm9tLCBidXQKICAgICMjIHRoZSBib3R0b20gcmlnaHQgaXMgdGhlIG5ldyBjb29yZGluYXRlIHNwYWNlLgogICAgIyMgc28gZmlyc3Qgd2hlbiB3ZSBnZXQgdGhlIG9yaWdpbmFsIGNvb3JkaW5hdGUgc3BhY2UsIHRvIHNldCB0byByZWxhdGl2ZQogICAgIyMgb2YgYm90dG9tIHdvdWxkIGJlIHRoZSBzYW1lIFgsIGJ1dCAxMDI0IC0gWQogICAgCiAgICAjIyBwdXNoIG91dCB0aGUgY29vcmRpbmF0ZXMgZm9yIGJldHRlciB2aXN1YWxpemF0aW9uCiAgICAjeF9yZXBlbGxlZCA8LSAoNTEyIC0gY29vcmRpbmF0ZXMkWF9Db29yZGluYXRlX0luX3BpeGVscykKICAgIAogICAgCiAgICBkZl8xNjRbZGZfMTY0JElNQUdFLk5BTUUgPT0gaW1hZ2VfbmFtZSwgJ1hfaG9yeiddID0gKGNvb3JkaW5hdGVzJFhfQ29vcmRpbmF0ZV9Jbl9waXhlbHMgLyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU1BR0VfU0laRSAqIElNQUdFX0xFTikgKyB5X2hvcnpbaV0KICAgIGRmXzE2NFtkZl8xNjQkSU1BR0UuTkFNRSA9PSBpbWFnZV9uYW1lLCAnWV9ob3J6J10gPSAoKDEwMjQtY29vcmRpbmF0ZXMkWV9Db29yZGluYXRlX0luX3BpeGVscykgLyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU1BR0VfU0laRSAqIElNQUdFX0xFTikgKyB4X2hvcnpbaV0KfQpgYGAKCmBgYHtyfQpoY29vcmRzID0gZGZfMTY0ICU+JSBkcGx5cjo6c2VsZWN0KGMoJ1hfaG9yeicsICdZX2hvcnonKSkgJT4lIGFzLm1hdHJpeCgpCmNvbG5hbWVzKGhjb29yZHMpIDwtIGMoJ3BpeGVsXzEnLCAncGl4ZWxfMicpCgpqeV8xNjRbWyJIIl1dIDwtIENyZWF0ZURpbVJlZHVjT2JqZWN0KGVtYmVkZGluZ3MgPSBoY29vcmRzLCBrZXkgPSAicGl4ZWxfIiwgYXNzYXkgPSBEZWZhdWx0QXNzYXkoanlfMTY0KSkKYGBgCgoKYGBge3J9Cmp5XzE2NDwtIFJlbmFtZUNlbGxzKGp5XzE2NCwgYyhvdXRlcignMTY0XycsIDE6bmNvbChqeV8xNjQpLCBGVU49cGFzdGUwKSkpCmp5XzE2NCRhcmVhID0gZGZfMTY0JGFyZWEKanlfNDA4PC0gUmVuYW1lQ2VsbHMoanlfNDA4LCBjKG91dGVyKCc0MDhfJywgMTpuY29sKGp5XzQwOCksIEZVTj1wYXN0ZTApKSkKanlfNDA4JGFyZWEgPSBkZl80MDgkYXJlYQpqeV9hbGwgPC0gbWVyZ2UoanlfMTY0LCBqeV80MDgpCmBgYApgYGB7cn0KanlfYWxsIDwtIE5vcm1hbGl6ZURhdGEoanlfYWxsLCBzY2FsZS5mYWN0b3IgPSAxZTUpICMjIwpub3JtZWQgPSBHZXRBc3NheURhdGEoanlfYWxsLCBzbG90ID0gJ2RhdGEnKQpub3JtZWRbbm9ybWVkIDwgM10gPSAwCmZvcihnZW5lX25hbWUgaW4gcm93bmFtZXMoanlfYWxsKSkgewogIGlmIChnZW5lX25hbWUgPT0gJ0RDWCcpewogICAgbWRuX2dlbmVfZXhwciA9IDAuNQogICAgcHJpbnQoJ3NraXAgZGN4JykKICB9IGVsc2UgaWYgKCFnZW5lX25hbWUgJWluJSBjKCdDT1VQVEYyJywgJ1NQOCcpKXsKICAgIG1kbl9nZW5lX2V4cHIgPSBtZWRpYW4obm9ybWVkW2dlbmVfbmFtZSwgbm9ybWVkW2dlbmVfbmFtZSwgXSA+IDBdKQogIH1lbHNlewogICAgbWRuX2dlbmVfZXhwciA9IHF1YW50aWxlKG5vcm1lZFtnZW5lX25hbWUsIG5vcm1lZFtnZW5lX25hbWUsIF0gPiAwXSwgLjQwKQogIH0KCiAgbm9ybWVkW2dlbmVfbmFtZSwgbm9ybWVkW2dlbmVfbmFtZSwgXSA8IG1kbl9nZW5lX2V4cHJdID0gMAp9Cmp5X2FsbCA8LSBTZXRBc3NheURhdGEoanlfYWxsLCBzbG90ID0gJ2RhdGEnLCBub3JtZWQpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0ID0gM30KanlfYWxsIDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGp5X2FsbCwgc2VsZWN0aW9uLm1ldGhvZCA9ICJ2c3QiKQphbGwuZ2VuZXMgPC0gcm93bmFtZXMoanlfYWxsKQpqeV9hbGwgPC0gU2NhbGVEYXRhKGp5X2FsbCwgZmVhdHVyZXMgPSBhbGwuZ2VuZXMpCmp5X2FsbCA8LSBSdW5QQ0EoanlfYWxsLCBhcHByb3ggPSBGQUxTRSkKanlfYWxsIDwtIEZpbmROZWlnaGJvcnMoanlfYWxsLCBkaW1zID0gMTozMCkKanlfYWxsIDwtIEZpbmRDbHVzdGVycyhqeV9hbGwsIHJlc29sdXRpb24gPSAwLjUpCmp5X2FsbCA8LSBSdW5VTUFQKGp5X2FsbCwgZGltcyA9IDE6MzApCgpEaW1QbG90KGp5X2FsbCwgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAnc2V1cmF0X2NsdXN0ZXJzJykgKyBOb0F4ZXMoKQpgYGAKCmBgYHtyfQpEaW1QbG90KGp5X2FsbCwgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAnYXJlYScpICsgTm9BeGVzKCkgIyBOb0xlZ2VuZCgpCmBgYApgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDN9CkZlYXR1cmVQbG90KGp5X2FsbCwgYygnQ1hDTDEyJywgJ0NYQ1I0JyksIGNlbGxzID0gd2hpY2goRmV0Y2hEYXRhKGp5X2FsbCwgJ0dBRDEnKSA+IDApLCBjb2xzID0gYyggJyNGMThGMDEnLCAnIzA0OEJBOCcpLCBibGVuZD0gVFJVRSkgCmBgYAoKCmBgYHtyfQpuZXcuY2x1c3Rlci5pZHMgPSBjKCdDR0UvTEdFJywKICAgICAgICAgICAgICAgICAgICAnRXgnLAogICAgICAgICAgICAgICAgICAgICdUQlIxKyBDR0UnLAogICAgICAgICAgICAgICAgICAgICdDQUxCMitETFgyKycsCiAgICAgICAgICAgICAgICAgICAgJ1ZJUCtHQUQxKycsCiAgICAgICAgICAgICAgICAgICAgJ1NTVCtMSFg2KycsCiAgICAgICAgICAgICAgICAgICAgJ01HRScpCgpgYGAKCmBgYHtyfQpuYW1lcyhuZXcuY2x1c3Rlci5pZHMpIDwtIGxldmVscyhqeV9hbGwpCmp5X2FsbCA8LSBSZW5hbWVJZGVudHMoanlfYWxsLCBuZXcuY2x1c3Rlci5pZHMpCmBgYAojIyBXaGF0IGFyZSBteSBuZWFyZXN0IG5laWdoYm9ycz8KCiMjIExldCdzIGp1c3QgbG9vayBhdCBpdCBzaW1wbHkuIFdoYXQgZG8gY2x1c3RlcnMgbG9vayBsaWtlPwojIyBXaGF0IGNlbGwgdHlwZXMgYXJlIGluIHRob3NlIGNsdXN0ZXJzPwojIyBEZWZpbml0ZWx5IGEgY2x1bXAgZ3JhcGgKIyMgU3RhY2tlZCBiYXJwbG90cyBpcyBhIHdheSB0byBnbwpgYGB7cn0KcGxvdF9jbHVzdGVyX2ZyYWN0aW9uIDwtIGZ1bmN0aW9uKHNvYmosIGNsdW1wX2lkKXsKICBhbGxfaWRlbnRzID0gbGV2ZWxzKElkZW50cyhzb2JqKSkKICBkZWZfY29sb3JzID0gaHVlX3BhbCgpKGxlbmd0aChhbGxfaWRlbnRzKSkKICAKICBjbHVtcF9vYmogPC0gc29ialssc29iaiRjbHVtcCA9PSBjbHVtcF9pZF0KICBjbHN0cnMgPC0gYXMuY2hhcmFjdGVyKElkZW50cyhjbHVtcF9vYmopKQogIGNsbXBfaWRzIDwtIGNsdW1wX29iaiRjbHVtcAogIGNsbXBfZGYgPC0gYXMuZGF0YS5mcmFtZShjYmluZChjbHN0cnMsIGNsbXBfaWRzKSkKICBjZWxsX2NvdW50ID0gbnJvdyhjbG1wX2RmKQogIGNvbG5hbWVzKGNsbXBfZGYpIDwtIGMoJ2NsdXN0ZXInLCAnY2x1bXAnKQogIAogIHdoaWNoX2NvbG9ycyA9IGRlZl9jb2xvcnNbd2hpY2goYWxsX2lkZW50cyAlaW4lIGNsc3RycyldCgogIGNsbXBfZGYgJT4lIGRwbHlyOjpjb3VudChjbHVtcCwgY2x1c3RlcikgJT4lCiAgICBnZ3Bsb3QoYWVzKHg9IiIsIHk9biwgZmlsbD1jbHVzdGVyKSkrIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5Iiwgd2lkdGg9MSwgY29sb3I9IndoaXRlIikgKwogIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQ9MCkgKwogICAgZ2d0aXRsZShwYXN0ZTAoY2x1bXBfaWQsICcsIG49JywgY2VsbF9jb3VudCkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHdoaWNoX2NvbG9ycykgKwogIHRoZW1lX3ZvaWQoKSsgTm9MZWdlbmQoKSArIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcsIHNpemUgPSByZWwoMC44KSwgaGp1c3QgPSAxKSkgIyByZW1vdmUgYmFja2dyb3VuZCwgZ3JpZCwgbnVtZXJpYyBsYWJlbHMKfQpwbG90X2NsdXN0ZXJfZnJhY3Rpb24oanlfYWxsLCAnMTY0X0NDXzhfMCcpCmBgYApgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoID0gMTV9CmNsbXBzID0gdW5pcXVlKGp5X2FsbCRjbHVtcCkKcGxvdHMgPC0gbGFwcGx5KDE6bGVuZ3RoKGNsbXBzKSwgZnVuY3Rpb24oaSl7CiAgICBwbG90X2NsdXN0ZXJfZnJhY3Rpb24oanlfYWxsLCBjbG1wc1tpXSkKICB9KQp1bWFwcyA9IHBsb3RfZ3JpZChwbG90bGlzdCA9IHBsb3RzLCBsYWJlbF9zaXplID0gMywgbnJvdyA9IDIzKQp1bWFwcwpgYGAKCgojIyB0cnkgYSBzdGFja2VkIGJhcnBsb3QKYGBge3J9CmFDbHVtcERGIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoYXMuY2hhcmFjdGVyKElkZW50cyhqeV9hbGwpKSwganlfYWxsJGNsdW1wLCBqeV9hbGwkYXJlYSkpCmNvbG5hbWVzKGFDbHVtcERGKSA8LSBjKCdjdHlwZScsICdjbHVtcCcsICdhcmVhJykKCmcxIDwtIGFDbHVtcERGICU+JSBmaWx0ZXIoY2x1bXAgIT0gTmFOKSAlPiUKICBkcGx5cjo6Y291bnQoY2x1bXAsIGN0eXBlKSAlPiUgCmdncGxvdChhZXMoY2x1bXAsIG4sIGZpbGw9Y3R5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQpnMiA8LSBhQ2x1bXBERiAlPiUgZmlsdGVyKGNsdW1wID09IE5hTikgJT4lCiAgZHBseXI6OmNvdW50KGNsdW1wLCBjdHlwZSkgJT4lIApnZ3Bsb3QoYWVzKGN0eXBlLCB5ID0gbiwgZmlsbCA9IGN0eXBlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyBSb3RhdGVkQXhpcygpICsgTm9MZWdlbmQoKQpnMgpgYGAKYGBge3J9CmFDbHVtcERGICU+JSBmaWx0ZXIoY2x1bXAgIT0gTmFOKSAlPiUKICBkcGx5cjo6Y291bnQoY2x1bXAsIGN0eXBlKSAlPiUgCmdncGxvdChhZXMoY3R5cGUsIHkgPSBuLCBmaWxsID0gY3R5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIFJvdGF0ZWRBeGlzKCkgKyBOb0xlZ2VuZCgpCmBgYApgYGB7cn0KIyMgSSB3YW50IHRvIHN1bW1hcml6ZSB0aGUgcmF0aW8gb2YgbmFuIHRvIG5vbi1uYW4KYGBgCgpgYGB7cn0KYUNsdW1wREYgJT4lIGZpbHRlcihjbHVtcCAhPSBOYU4pICU+JQogIGRwbHlyOjpjb3VudChjdHlwZSwgYXJlYSwgY2x1bXApICU+JSAKZ2dwbG90KGFlcyhhcmVhLCBuLCBmaWxsPWN0eXBlKSkgKyBSb3RhdGVkQXhpcygpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpCmBgYAoKYGBge3J9CmFDbHVtcERGICU+JSBmaWx0ZXIoY2x1bXAgIT0gTmFOKSAlPiUKICBkcGx5cjo6Y291bnQoYXJlYSwgY2x1bXApICU+JQogICBnZ3Bsb3QoYWVzKGFyZWEsIG4pKSArIAogIGdlb21fYm94cGxvdCgpICsgUm90YXRlZEF4aXMoKQpgYGAKCgpgYGB7cn0KTW9kZSA8LSBmdW5jdGlvbih4KSB7CiAgdXggPC0gdW5pcXVlKHgpCiAgdXhbd2hpY2gubWF4KHRhYnVsYXRlKG1hdGNoKHgsIHV4KSkpXQp9CgphQ2x1bXBERiAlPiUgZ3JvdXBfYnkoY2x1bXApICU+JSBtdXRhdGUodG9wX2N0eXBlPU1vZGUoY3R5cGUpKQphQ2x1bXBERiR0b3BfY3R5cGUgPC0gIHdpdGgoYUNsdW1wREYsIGF2ZShjdHlwZSwgY2x1bXAsIEZVTj1Nb2RlKSkKYUNsdW1wREYkdG9wX2N0eXBlCmBgYApgYGB7ciwgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDh9CgogCiMgQ3JlYXRlIGRhdGEKI2NsdXN0ZXJzIDwtIGFDbHVtcERGICU+JSBmaWx0ZXIoZ3JlcGwoJ01TJywgYXJlYSkpICU+JSBkcGx5cjo6Y291bnQoYXJlYSwgY2x1bXApICU+JSBmaWx0ZXIoY2x1bXAgIT0gTmFOKQpjbHVzdGVycyA8LSBhQ2x1bXBERiAlPiUgZHBseXI6OmNvdW50KGN0eXBlLCBjbHVtcCkgJT4lIGZpbHRlcihjbHVtcCAhPSBOYU4pCiAKIyBHZW5lcmF0ZSB0aGUgbGF5b3V0LiBUaGlzIGZ1bmN0aW9uIHJldHVybiBhIGRhdGFmcmFtZSB3aXRoIG9uZSBsaW5lIHBlciBidWJibGUuIAojIEl0IGdpdmVzIGl0cyBjZW50ZXIgKHggYW5kIHkpIGFuZCBpdHMgcmFkaXVzLCBwcm9wb3J0aW9uYWwgb2YgdGhlIHZhbHVlCnBhY2tpbmcgPC0gY2lyY2xlUHJvZ3Jlc3NpdmVMYXlvdXQoY2x1c3RlcnMkbiwgc2l6ZXR5cGU9J2FyZWEnKQogCiMgV2UgY2FuIGFkZCB0aGVzZSBwYWNraW5nIGluZm9ybWF0aW9uIHRvIHRoZSBpbml0aWFsIGRhdGEgZnJhbWUKY2x1c3RlcnMgPC0gY2JpbmQoY2x1c3RlcnMsIHBhY2tpbmcpCiAKIyBDaGVjayB0aGF0IHJhZGl1cyBpcyBwcm9wb3J0aW9uYWwgdG8gdmFsdWUuIFdlIGRvbid0IHdhbnQgYSBsaW5lYXIgcmVsYXRpb25zaGlwLCBzaW5jZSBpdCBpcyB0aGUgQVJFQSB0aGF0IG11c3QgYmUgcHJvcG9ydGlvbm5hbCB0byB0aGUgdmFsdWUKIyBwbG90KGRhdGEkcmFkaXVzLCBkYXRhJHZhbHVlKQogCiMgVGhlIG5leHQgc3RlcCBpcyB0byBnbyBmcm9tIG9uZSBjZW50ZXIgKyBhIHJhZGl1cyB0byB0aGUgY29vcmRpbmF0ZXMgb2YgYSBjaXJjbGUgdGhhdAojIGlzIGRyYXduIGJ5IGEgbXVsdGl0dWRlIG9mIHN0cmFpZ2h0IGxpbmVzLgpjbHVzdGVycy5nZyA8LSBjaXJjbGVMYXlvdXRWZXJ0aWNlcyhwYWNraW5nLCBucG9pbnRzPTUwKQpjbHVzdGVycy5nZyRpZGVudHMgPC0gcmVwKGNsdXN0ZXJzJGN0eXBlLCBlYWNoPTUxKQogCiMgTWFrZSB0aGUgcGxvdApnZ3Bsb3QoKSArIAogIAogICMgTWFrZSB0aGUgYnViYmxlcwogIGdlb21fcG9seWdvbihkYXRhID0gY2x1c3RlcnMuZ2csIGFlcyh4LCB5LCBncm91cCA9IGlkLCBmaWxsPWFzLmZhY3RvcihpZGVudHMpKSwgY29sb3VyID0gImJsYWNrIiwgYWxwaGEgPSAwLjkpICsKICAKICAjIEFkZCB0ZXh0IGluIHRoZSBjZW50ZXIgb2YgZWFjaCBidWJibGUgKyBjb250cm9sIGl0cyBzaXplCiAgZ2VvbV90ZXh0KGRhdGEgPSBjbHVzdGVycywgYWVzKHgsIHksIHNpemU9biwgbGFiZWwgPSBjbHVtcCkpICsKICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsMykpICsKICAKICAjIEdlbmVyYWwgdGhlbWU6CiAgdGhlbWVfdm9pZCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIGNvb3JkX2VxdWFsKCkKYGBgCmBgYHtyfQpjbHVtcF9tZXRhIDwtIGRhdGEuZnJhbWUoKQoKanlfY2x1bXAgPSBqeV9hbGxbLCBqeV9hbGwkY2x1bXAhPSBOYU5dCnRpYygpCmZvcihpIGluIDE6bmNvbChqeV9jbHVtcCkpewogICMgSXRlcmF0ZSB0aHJvdWdoIHRoZSBjZWxscywgZ2V0IHRoZSBuZWlnaGJvcnMgZm9yIHRoYXQgcGFydGljdWxhciBjZWxsCiAgY2VsbCA8LSBjb2xuYW1lcyhqeV9jbHVtcFssaV0pCiAgY2x1bXAgPC0ganlfY2x1bXBbLCBqeV9jbHVtcCRjbHVtcCA9PSBqeV9jbHVtcCRjbHVtcFtpXV0KICBuZWlnaGJvcnMgPC0gY2x1bXBAbWV0YS5kYXRhICU+JSBmaWx0ZXIoY2x1bXAgIT0gY2VsbCkKICBwcm9icyA9IHJlcCgwLCBsZW5ndGgobGV2ZWxzKGp5X2FsbCkpKQogIG5laWdoYm9yX2lkcyA8LSBhcy5udW1lcmljKG5laWdoYm9ycyRzZXVyYXRfY2x1c3RlcnMpCiAgIyBDYWxjdWxhdGUgdGhlIHByb2JhYmlsaXR5IG9mIGVhY2ggY2x1c3RlciB0eXBlCiAgZm9yKGogaW4gMTpsZW5ndGgodW5pcXVlKG5laWdoYm9yX2lkcykpKXsKICAgIGpfY2xhc3MgPSB1bmlxdWUobmVpZ2hib3JfaWRzKVtqXQogICAgcHJvYnNbal9jbGFzc10gPSBzdW0obmVpZ2hib3JfaWRzID09IGpfY2xhc3MpIC8gbnJvdyhuZWlnaGJvcnMpCiAgfQoKICBjbHVtcF9tZXRhIDwtIHJiaW5kKGNsdW1wX21ldGEsIHByb2JzKQogIGNvbG5hbWVzKGNsdW1wX21ldGEpIDwtIGMob3V0ZXIoJ3AnLCAxOmxlbmd0aChsZXZlbHMoanlfYWxsKSksIEZVTj1wYXN0ZTApKQp9CnRvYygpCmNsdW1wX21ldGEKYGBgCmBgYHtyfQpjbHVtcHMgPC0gYXMuZGF0YS5mcmFtZShjYmluZChqeV9jbHVtcEBtZXRhLmRhdGEsIGNsdW1wX21ldGEpKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDE0fQpyZWZfY2x1c3RlciA9IDEKcGxvdHMgPSBsaXN0KCkKCmZvcihpIGluIDE6bGVuZ3RoKGxldmVscyh1bmlxdWUoY2x1bXBzJHNldXJhdF9jbHVzdGVycykpKSl7CiAgcmVmX2NsdXN0ZXIgPSBpCiAgcGxvdHNbW2ldXSA8LSBjbHVtcHMgJT4lCiAgZmlsdGVyKGFzLm51bWVyaWMoc2V1cmF0X2NsdXN0ZXJzKSA9PSByZWZfY2x1c3RlcikgJT4lCiAgZHBseXI6OnNlbGVjdChjKHNldXJhdF9jbHVzdGVycywgcDEsIHAyLCBwMywgcDQsIHA1LCBwNixwNyxwOCxwOSxwMTAscDExLHAxMixwMTMpKSAlPiUKICBwaXZvdF9sb25nZXIoIXNldXJhdF9jbHVzdGVycywgbmFtZXNfdG8gPSAiY2xhc3NlcyIsIHZhbHVlc190byA9ICJwcm9iIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcHJvYiwgZmlsbCA9IGZhY3RvcihjbGFzc2VzLCBsZXZlbHMgPSBjKG91dGVyKCdwJywgMTpsZW5ndGgobGV2ZWxzKGp5X2FsbCkpLCBGVU49cGFzdGUwKSkpKSkgKyBnZW9tX2JveHBsb3Qobm90Y2ggPSBGQUxTRSkgKyAKICBnZ3RpdGxlKHBhc3RlKGxldmVscyhqeV9hbGwpW3JlZl9jbHVzdGVyXSwgInZzIG90aGVyIikpICsgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gJ3N1YnR5cGVzJywgbGFiZWxzPWxldmVscyhJZGVudHMoanlfYWxsKSkpCiB9CgpwbG90X2dyb3VwZWQgPC0gbWFycmFuZ2VHcm9iKHBsb3RzLCBucm93PTIsIG5jb2w9MikKZ2dzYXZlKGZpbGVuYW1lID0gJ2NlbGxfc3BlY2lmaWNfY2x1c3RlcmluZy5wZGYnLCBwYXRoID0gZmlsZS5wYXRoKG91dHB1dF9kaXJfcGxvdCwgJzIwMjIwODA4XzEnKSwgcGxvdF9ncm91cGVkLCBoZWlnaHQgPSAxMCwgd2lkdGggPSAxNCwgZHBpID0gMTUwKQpgYGAKYGBge3J9CnJlZl9jbHVzdGVyID0gMgoKY2x1bXBzICU+JQogIGZpbHRlcihjbHVzdGVyID09IHJlZl9jbHVzdGVyKSAlPiUKICBzZWxlY3QoYyhpbWFnZSwgcDAsIHAxLCBwMiwgcDMsIHA0LCBwNSwgcDYpKSAlPiUKICBwaXZvdF9sb25nZXIoIWNsdXN0ZXIsIG5hbWVzX3RvID0gImNsYXNzZXMiLCB2YWx1ZXNfdG8gPSAicHJvYiIpICU+JQogIGdncGxvdChhZXMoeCA9IHByb2IsIGNvbG9yID0gY2xhc3NlcykpICsgZ2VvbV9ib3hwbG90KCkgKyAKICBnZ3RpdGxlKHBhc3RlKCJDbHVzdGVyIixhcy5jaGFyYWN0ZXIocmVmX2NsdXN0ZXItMSksICJ2cyBvdGhlciIpKQpgYGAKCmBgYHtyfQpwbWF0IDwtIGNsdW1wcyAlPiUKICAgIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGMocDEsIHAyLCBwMywgcDQsIHA1LCBwNixwNyxwOCxwOSxwMTAscDExLHAxMixwMTMpKSAlPiUKICAgIGRwbHlyOjpzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgbGlzdChtZWFuKSkpICU+JQogICAgZHBseXI6OnNlbGVjdCgtc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgIGFzLm1hdHJpeCgpCgpyb3duYW1lcyhwbWF0KSA8LSBsZXZlbHMoSWRlbnRzKGp5X2FsbCkpCmNvbG5hbWVzKHBtYXQpIDwtIHJvd25hbWVzKHBtYXQpCmBgYAoKYGBge3IgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDh9CnBoZWF0bWFwKHBtYXQsIGNvbG9yID0gdHVyYm8obiA9IDUwKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gNn0KbGlicmFyeSgidmlyaWRpcyIpICAgICAgICAgICAjIExvYWQKcGhlYXRtYXAocG1hdCxjb2xvciA9IHJvY2tldChuID0gNTApLCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgY2x1c3Rlcl9jb2xzID0gRkFMU0UpCmBgYApgYGB7cn0KIyMgSSB3YW50IHRvIGtub3cgdGhlIGV4cHJlc3Npb24gb2YgZGlmZmVyZW50IGdlbmVzIGJ5IHRoZSBkaWZmZXJlbnQgYXJlYXMKIyMgYW5kIGNsdW1wcyBpbiB0aG9zZSByZXNwZWN0aXZlIGFyZWFzCgojIyBTbyBiYXNpY2FsbHkgNCBwbG90cywgYW50ZXJpb3IgZG9yc2FsLCB2ZW50cmFsLCBhbmQgcG9zdGVyaW9yCiMjIFRoZW4gbmVlZCB0aGUgY2x1bXAgbmFtZSBwZXIgdGhpbmctYS1tYS1ib2IKIyMgQ2x1bXAgdmVyc3VzIG5vIGNsdW1wCiMjIGRpZmZlcmVuY2VzIGJldHdlZW4gY2x1bXBzICh0b3BfY3R5cGUgcGVyIGNsdW1wKQpgYGAKCmBgYHtyfQojIyBTdGFydCB3aXRoIHRoZSBiYXJjaGFydHMKCnBsb3RfZnVuY3Rpb25hbF9nZW5lcyA8LSBmdW5jdGlvbihzb2JqLCBncm91cF9rZXksIGdyb3VwID0gJ2FyZWEnLCBmeG5fZ2VuZV9saXN0ID0gTlVMTCkgewogICMjIFNvIEkgbmVlZCB0byBtYWtlIGEgYmFyY2hhcnQgd2l0aCB0aGUgYXZlcmFnZSBleHByZXNzaW9uIG9mIHRoZSBwYXJ0aWN1bGFyCiAgIyMgZ2VuZSBsaXN0IHRoZSBJIGNhcmUgYWJvdXQuIFRoaXMgY2FuIGJlIGhhcmRjb2RlZC4uLiBoZXJlPwogIGlmKGlzLm51bGwoZnhuX2dlbmVfbGlzdCkpewogICAgZnhuX2dlbmVfbGlzdCA9IGMoJ1ZMRExSJywgJ0xSUDgnLCAnQ1hDUjQnLCAnQ1hDUjcnLCAnQ1hDTDEyJywgJ0NYQ0wxNCcsICdOQ0FNMScpCiAgfQogIHNic2V0X29iaiA9IHNvYmpbLCBzb2JqW1tncm91cF1dID09IGdyb3VwX2tleV0KICAjIyBJIHdhbnQgdG8gcGxvdCB0aGVpciAiY2x1bXBlZG5lc3MsIiBzbyByZWFsbHkgaXQncyBlaXRoZXIgY2x1bXAgb3Igbm8gY2x1bXAgYXMgYSBncm91cAogIGdlbmVfZXhwciA9IGFzLmRhdGEuZnJhbWUoRmV0Y2hEYXRhKHNic2V0X29iaiwgZnhuX2dlbmVfbGlzdCkpCiAgZ2VuZV9leHByJGNsdW1wID0gc2JzZXRfb2JqJGNsdW1wICE9ICJOYU4iCiAgZ2VuZV9leHByICU+JQogICAgcGl2b3RfbG9uZ2VyKCFjbHVtcCwgbmFtZXNfdG8gPSAnZ2VuZScsIHZhbHVlc190byA9ICdleHByJykgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBnZW5lLCB5ID0gZXhwciwgZmlsbCA9IGNsdW1wKSkgKwogICAgZ2VvbV9ib3hwbG90KCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBnZ3RpdGxlKGdyb3VwX2tleSkgKyBSb3RhdGVkQXhpcygpCn0KcGxvdF9mdW5jdGlvbmFsX2dlbmVzKGp5X2FsbCwgJzE2NF9UQycpCmBgYApgYGB7ciBmaWcuaGVpZ2h0ID0gOSwgZmlnLndpZHRoID0gMTJ9CiMjIEZvciBhcmVhcwphcmVhX2xpc3QgPSB1bmlxdWUoanlfYWxsJGFyZWEpCnBsb3RzID0gbGlzdCgpCnBsb3RzIDwtIGxhcHBseSgxOmxlbmd0aChhcmVhX2xpc3QpLCBmdW5jdGlvbihpKXsKICAgIHBsb3RfZnVuY3Rpb25hbF9nZW5lcyhqeV9hbGwsIGFyZWFfbGlzdFtpXSkKICB9KQp2bG5wbG90cyA9IHBsb3RfZ3JpZChwbG90bGlzdCA9IHBsb3RzLCBsYWJlbF9zaXplID0gMywgbnJvdyA9IDkpCnZsbnBsb3RzCiNnZ3NhdmUoZmlsZW5hbWUgPSAnaW1nX2Z4bmFsX2dlbmVfY2x1bXBzLnBkZicsIHBhdGggPSBmaWxlLnBhdGgob3V0cHV0X2Rpcl9wbG90LCAnMjAyMjA4MTdfMScpLCB2bG5wbG90cywgaGVpZ2h0ID0gOSwgd2lkdGggPSAxMiwgZHBpID0gMTUwKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQgPSAxOCwgZmlnLndpZHRoID0gMTJ9CiMjIGZvciBpbWFnZXMKanlfYWxsJGltYWdlID0gYyhkZl8xNjQkSU1BR0UuTkFNRSwgZGZfNDA4JElNQUdFLk5BTUUpCmltYWdlX2xpc3QgPSB1bmlxdWUoanlfYWxsJGltYWdlKQpwbG90cyA9IGxpc3QoKQpwbG90cyA8LSBsYXBwbHkoMTpsZW5ndGgoaW1hZ2VfbGlzdCksIGZ1bmN0aW9uKGkpewogICAgcGxvdF9mdW5jdGlvbmFsX2dlbmVzKGp5X2FsbCwgaW1hZ2VfbGlzdFtpXSwgZ3JvdXAgPSAnaW1hZ2UnKQogIH0pCmJ4cGxvdHMgPSBwbG90X2dyaWQocGxvdGxpc3QgPSBwbG90cywgbGFiZWxfc2l6ZSA9IDMsIG5yb3cgPSAxMikKYnhwbG90cwpnZ3NhdmUoZmlsZW5hbWUgPSAnaW1nX2Z4bmFsX2dlbmVfY2x1bXBzLnBkZicsIHBhdGggPSBmaWxlLnBhdGgob3V0cHV0X2Rpcl9wbG90LCAnMjAyMjA4MTdfMScpLCBieHBsb3RzLCBoZWlnaHQgPSAxOCwgd2lkdGggPSAxMiwgZHBpID0gMTUwKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoID0gOH0KcGxvdF9jbHVtcF9leHByIDwtIGZ1bmN0aW9uKHNvYmosIGNsdW1wX25hbWUsIGdlbmUsIHB0X3NpemUgPSA4LCBJTUFHRV9TUEVDSUZJQyA9IEZBTFNFKSB7CgogIHNic2V0X29iaiA9IHNvYmpbLCBkZl80MDgkSU1BR0UuTkFNRSA9PSBjbHVtcF9uYW1lXQogIHh5ID0gRW1iZWRkaW5ncyhzYnNldF9vYmosIHJlZHVjdGlvbiA9ICdIJykKICAjZXhwbWF0ICA9IGFzLmNoYXJhY3RlcihJZGVudHMoc2JzZXRfb2JqKSkKICBleHBtYXQgPSBGZXRjaERhdGEoc2JzZXRfb2JqLCBnZW5lKQogIGRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoeHksIGV4cG1hdCkpCiAgY29sbmFtZXMoZGYpIDwtIGMoJ3gnLCAneScsICdpZGVudCcpCiAgbGlnYW5kcyA9IGMoJ0NYQ0wxMicsICdDWENMMTQnLCAnUkVMTicpCiAgcmVjZXB0b3JzID0gYygnQ1hDUjQnLCAnQ1hDUjcnLCAnTFJQOCcsICdWTERMUicsICdFR0ZSJykKICBtYXR1cml0eSA9IGMoJ1ZJUCcsICdTU1QnKQogIGFkaGVzaW9uID0gYygnTkNBTTEnKQogIGltbWF0dXJpdHkgPSBjKCdEQ1gnKQogIGRpc2Vhc2UgPSBjKCdEQ0RDMicsICdLSUEwMzE5JykKICAKICBjb2xvcl9kZiA9IGFzLmRhdGEuZnJhbWUocm93bmFtZXMoc29iaikpCiAgY29sbmFtZXMoY29sb3JfZGYpID0gJ2dlbmUnCiAgY29sb3JfZGYkY29sb3IgPSAnIzA3NjhGQScKICBjb2xvcl9kZiRjb2xvcltjb2xvcl9kZiRnZW5lICVpbiUgbGlnYW5kc10gPSAnI0ZGRkI0NicKICBjb2xvcl9kZiRjb2xvcltjb2xvcl9kZiRnZW5lICVpbiUgcmVjZXB0b3JzXSA9ICcjRjI2NDE5JwogIGNvbG9yX2RmJGNvbG9yW2NvbG9yX2RmJGdlbmUgJWluJSBtYXR1cml0eV0gPSAnIzQ1MDkyMCcKICBjb2xvcl9kZiRjb2xvcltjb2xvcl9kZiRnZW5lICVpbiUgYWRoZXNpb25dID0gJyNGNDJDMDQnCiAgY29sb3JfZGYkY29sb3JbY29sb3JfZGYkZ2VuZSAlaW4lIGltbWF0dXJpdHldID0gJyMwQ0Y3MDAnCiAgY29sb3JfZGYkY29sb3JbY29sb3JfZGYkZ2VuZSAlaW4lIGRpc2Vhc2VdID0gJyM4RkIzNTYnCiAgCiAgCiAgaWYgKElNQUdFX1NQRUNJRklDKXsKICAgIHRpdGxlX25hbWUgPSBjbHVtcF9uYW1lCiAgfSBlbHNlewogICAgdGl0bGVfbmFtZSA9IGdlbmUKICB9CiAgCiAgI2NvbG9ycyA9IGh1ZV9wYWwoKShsZW5ndGgobGV2ZWxzKElkZW50cyhqeV9hbGwpKSkpCiAgI2NvbG9ycyA9IGNvbG9yc1t3aGljaChsZXZlbHMoSWRlbnRzKGp5X2FsbCkpICVpbiUgZGYkaWRlbnQpXQogIGNvbG9ycyA9IGMoJ2dyZXk5MCcsICdncmV5OTAnLCBjb2xvcl9kZiRjb2xvcltjb2xvcl9kZiRnZW5lID09IGdlbmVdKQogIGRmJHggPSBhcy5udW1lcmljKGRmJHgpCiAgZGYkeSA9IGFzLm51bWVyaWMoZGYkeSkKICBkZiRjbHVtcCA9IHNic2V0X29iaiRjbHVtcAogIGRmJGlkZW50X2xhYmVsID0gYXMuY2hhcmFjdGVyKHJvdW5kKGRmJGlkZW50LCBkaWdpdHMgPSAyKSkKICBkZiAlPiUKICAgICNnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBjbHVtcCkpICsKICAgICNnZW9tX3BvaW50KHNpemUgPSA4KSArIHRoZW1lX2NsYXNzaWMoKSArIGdndGl0bGUoY2x1bXBfbmFtZSkgKyBjb29yZF9maXhlZChyYXRpbyA9IDEpICsgTm9BeGVzKCkgICMjIHdpdGhvdXQgY2VsbCB0eXBlCiAgICAjZ2dwbG90KGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gZmFjdG9yKGlkZW50LCBsZXZlbHMgPSBsZXZlbHMoSWRlbnRzKHNvYmopKSkpKSArCiAgICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBpZGVudCkpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IHB0X3NpemUpICsgdGhlbWVfY2xhc3NpYygpICsgZ2d0aXRsZSh0aXRsZV9uYW1lKSArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSkgKyBOb0F4ZXMoKSArICNzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JzKSArCiAgICBnZW9tX2VuY2lyY2xlKGRhdGEgPSBmaWx0ZXIoZGYsIGNsdW1wICE9ICJOYU4iKSwgZXhwYW5kID0gMC4wMyxhZXMoZ3JvdXAgPSBjbHVtcCkgKSArIHNjYWxlX2NvbG9yX2dyYWRpZW50KG5hLnZhbHVlID0gY29sb3JzWzFdLCBsb3cgPSBjb2xvcnNbMl0sIGhpZ2ggPSBjb2xvcnNbM10pICMrIGdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IGZpbHRlcihkZiwgaWRlbnQgPiAwKSwgbGFiZWwgPSBmaWx0ZXIoZGYsIGlkZW50ID4gMClbWydpZGVudF9sYWJlbCddXSkgCiAgIysgc2NhbGVfY29sb3JfZ3JhZGllbnQobGltaXRzID0gYygwLDExKSkKCn0KCnBsb3RfY2x1bXBfZXhwcihqeV80MDgsICdUQ185JywgJ0dBRDEnKQpgYGAKCgpgYGB7cn0KcGxvdF9jbHVtcF9jbHVzdCA8LSBmdW5jdGlvbihzb2JqLCBjbHVtcF9uYW1lLCBwdF9zaXplID0gOCkgewoKICBzYnNldF9vYmogPSBzb2JqWywgZGZfNDA4JElNQUdFLk5BTUUgPT0gY2x1bXBfbmFtZV0KICB4eSA9IEVtYmVkZGluZ3Moc2JzZXRfb2JqLCByZWR1Y3Rpb24gPSAnSCcpCiAgI2V4cG1hdCAgPSBhcy5jaGFyYWN0ZXIoSWRlbnRzKHNic2V0X29iaikpCiAgZXhwbWF0ID0gRmV0Y2hEYXRhKHNic2V0X29iaiwgZ2VuZSkKICBkZiA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKHh5LCBleHBtYXQpKQogIGNvbG5hbWVzKGRmKSA8LSBjKCd4JywgJ3knLCAnaWRlbnQnKQogIAogICNjb2xvcnMgPSBodWVfcGFsKCkobGVuZ3RoKGxldmVscyhJZGVudHMoanlfYWxsKSkpKQogICNjb2xvcnMgPSBjb2xvcnNbd2hpY2gobGV2ZWxzKElkZW50cyhqeV9hbGwpKSAlaW4lIGRmJGlkZW50KV0KICBjb2xvcnMgPSBjKCdncmV5OTAnLCAnZ3JleTkwJywgY29sb3JfZGYkY29sb3JbY29sb3JfZGYkZ2VuZSA9PSBnZW5lXSkKICBkZiR4ID0gYXMubnVtZXJpYyhkZiR4KQogIGRmJHkgPSBhcy5udW1lcmljKGRmJHkpCiAgZGYkY2x1bXAgPSBzYnNldF9vYmokY2x1bXAKICBkZiAlPiUKICAgIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IGNsdW1wKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gcHRfc2l6ZSkgKyB0aGVtZV9jbGFzc2ljKCkgKyBnZ3RpdGxlKGNsdW1wX25hbWUpICsgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArIE5vQXhlcygpICsgICMjIHdpdGhvdXQgY2VsbCB0eXBlCiAgICAjZ2dwbG90KGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gZmFjdG9yKGlkZW50LCBsZXZlbHMgPSBsZXZlbHMoSWRlbnRzKHNvYmopKSkpKSArCiAgICAjZ2dwbG90KGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gaWRlbnQpKSArCiAgICAjZ2VvbV9wb2ludChzaXplID0gOCkgKyB0aGVtZV9jbGFzc2ljKCkgKyBnZ3RpdGxlKGNsdW1wX25hbWUpICsgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArIE5vQXhlcygpICsgI3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcnMpICsKICAgIGdlb21fZW5jaXJjbGUoZGF0YSA9IGZpbHRlcihkZiwgY2x1bXAgIT0gIk5hTiIpLCBleHBhbmQgPSAwLjAzLGFlcyhncm91cCA9IGNsdW1wKSApCgp9CgpwbG90X2NsdW1wX2NsdXN0KGp5XzQwOCwgJ1RDXzExJykKCmBgYAoKYGBge3J9CnBsb3RfY2x1bXBfY2VsbHR5cGUgPC0gZnVuY3Rpb24oc29iaiwgY2x1bXBfbmFtZSwgIHB0X3NpemUgPSA4KSB7CgogIHNic2V0X29iaiA9IHNvYmpbLCB3aGljaChzb2JqJGltYWdlID09IGNsdW1wX25hbWUpXQogIHh5ID0gRW1iZWRkaW5ncyhzYnNldF9vYmosIHJlZHVjdGlvbiA9ICdIJykKICBleHBtYXQgID0gYXMuY2hhcmFjdGVyKElkZW50cyhzYnNldF9vYmopKQogICNleHBtYXQgPSBGZXRjaERhdGEoc2JzZXRfb2JqLCBnZW5lKQogIGRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoeHksIGV4cG1hdCkpCiAgY29sbmFtZXMoZGYpIDwtIGMoJ3gnLCAneScsICdpZGVudCcpCiAgY29sb3JzID0gaHVlX3BhbCgpKGxlbmd0aChsZXZlbHMoSWRlbnRzKGp5X2FsbCkpKSkKICBjb2xvcnMgPSBjb2xvcnNbd2hpY2gobGV2ZWxzKElkZW50cyhqeV9hbGwpKSAlaW4lIGRmJGlkZW50KV0KICAjY29sb3JzID0gYygnZ3JleTkwJywgJ2dyZXk5MCcsIGNvbG9yX2RmJGNvbG9yW2NvbG9yX2RmJGdlbmUgPT0gZ2VuZV0pCiAgZGYkeCA9IGFzLm51bWVyaWMoZGYkeCkKICBkZiR5ID0gYXMubnVtZXJpYyhkZiR5KQogIGRmJGNsdW1wID0gc2JzZXRfb2JqJGNsdW1wCiAjIG15cGxvdCA8LSAKICAgIGRmICU+JQogICAgZ2dwbG90KGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gZmFjdG9yKGlkZW50LCBsZXZlbHMgPSBsZXZlbHMoSWRlbnRzKHNvYmopKSkpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSBwdF9zaXplKSArIHRoZW1lX2NsYXNzaWMoKSArIyBnZ3RpdGxlKGNsdW1wX25hbWUpICsgCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsgTm9BeGVzKCkgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JzLCBuYW1lID0gJ2NsdXN0ZXInKSArIAogICAgZ2VvbV9lbmNpcmNsZShkYXRhID0gZmlsdGVyKGRmLCBjbHVtcCAhPSAiTmFOIiksIGV4cGFuZCA9IDAuMDMsYWVzKGdyb3VwID0gY2x1bXApICkgKyB4bGltKDAsIDMyKSArIHlsaW0oNTI1LCA1NTgpICsgICB0aGVtZV9jb3dwbG90KCkgKyBOb0F4ZXMoKSArIE5vTGVnZW5kKCkjKyAKICAgICN0aGVtZShsZWdlbmQua2V5LnNpemU9dW5pdCgwLjEsJ21tJyksCiAgICAjICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTQpLAogICAgIyAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9NikpIysgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbG9ycywgbmFtZSA9ICdjbHVzdGVyJywgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTQpKSkgKwojKyBzY2FsZV9jb2xvcl9ncmFkaWVudChuYS52YWx1ZSA9IGNvbG9yc1sxXSwgbG93ID0gY29sb3JzWzJdLCBoaWdoID0gY29sb3JzWzNdKQogICMrIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxpbWl0cyA9IGMoMCwxMSkpCiAgI3JldHVybihteXBsb3QpCn0KanlfNDA4JGNsdW1wID0ganlfYWxsJGNsdW1wX3Bsb3RbKG5jb2woanlfMTY0KSsxKTpuY29sKGp5X2FsbCldCiNqeV80MDgkaW1hZ2UgPSBqeV9hbGwkaW1hZ2VbKG5jb2woanlfMTY0KSsxKTpuY29sKGp5X2FsbCldCmp5XzE2NCRjbHVtcCA9IGp5X2FsbCRjbHVtcF9wbG90WzE6bmNvbChqeV8xNjQpXQojanlfMTY0JGltYWdlID0ganlfYWxsJGltYWdlWzE6bmNvbChqeV8xNjQpXQpwbG90X2NsdW1wX2NlbGx0eXBlKGp5XzQwOCwgJ1RDXzMnLCBwdF9zaXplID0gMykKYGBgCgpgYGB7cn0KZGFwaV9pbWcgPSBpbWFnZV9yZWFkKCcvaG9tZS9hdW5veS9zdC9hcmNfcHJvZmlsaW5nL3N0X2FuYWx5c2lzL2hhbmRfYW5ub3RhdGVkX2RhdGEvdGVzdC80MDhfdGMzLnRpZicpCgpmaWcgPC0gaW1hZ2VfZ3JhcGgod2lkdGggPSAxMDI0LCBoZWlnaHQgPSAxMDI0LCByZXMgPSA5NikKbXlwbG90IDwtIHBsb3RfY2x1bXBfY2VsbHR5cGUoanlfNDA4LCAnVENfMycsIHB0X3NpemUgPSAzKQpkZXYub2ZmKCkKYGBgCmBgYHtyfQpkYXBpX2ltZyA9IGltYWdlX3JlYWQoJy9ob21lL2F1bm95L3N0L2FyY19wcm9maWxpbmcvc3RfYW5hbHlzaXMvaGFuZF9hbm5vdGF0ZWRfZGF0YS90ZXN0LzQwOF90YzMudGlmJykKbXlwbG90IDwtIHBsb3RfY2x1bXBfY2VsbHR5cGUoanlfNDA4LCAnVENfMycsIHB0X3NpemUgPSA1KQpnZ2RyYXcoKSArCiAgZHJhd19pbWFnZShkYXBpX2ltZykgKwogIGRyYXdfcGxvdChteXBsb3QsIHNjYWxlID0gMS4xNCkKYGBgCgpgYGB7cn0Kbm9ybWVkID0gR2V0QXNzYXlEYXRhKGp5XzQwOCwgc2xvdCA9ICJkYXRhIikKCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMTgsIGZpZy53aWR0aCA9IDEyfQojIyBTbyB0aGUgbGF5b3V0IHRoYXQgSSBhbSBnb2luZyB0byB3YW50IGlzLi4uCiMjIHRoZXJlIGFyZSAzMiBnZW5lcyB0aGF0IEkgYW0gaW50ZXJlc3RlZCBpbiwgc28gb24gb25lIHBhZ2UgaWRlYWxseSB0aGF0IHdpbGwgYmUgNCBieSA4CiMjIEFuZCB0aGVuIHdlIGNhbiBzaG93IHRoZSBjZWxsIHR5cGUKanlfNDA4JGltYWdlID0gZGZfNDA4JElNQUdFLk5BTUUKanlfMTY0JGltYWdlID0gZGZfMTY0JElNQUdFLk5BTUUKcGxvdF9jbHVzdGVyX3Byb2ZpbGUgPC0gZnVuY3Rpb24oc29iaiwgaW1hZ2UsIHB0X3NpemUgPSA4KXsKICBnZW5lcyA9IHJvd25hbWVzKHNvYmopCiAgcGxvdHMgPSBsaXN0KCkKICBwbG90cyA8LSBsYXBwbHkoMTpsZW5ndGgoZ2VuZXMpLCBmdW5jdGlvbihpKXsKICAgIHBsb3RfY2x1bXBfZXhwcihzb2JqLCBpbWFnZSwgZ2VuZXNbaV0sIHB0X3NpemUgPSBwdF9zaXplKQogIH0pCiAgI3Bsb3RzID0gYXBwZW5kKGxpc3QocGxvdF9jbHVtcF9jbHVzdChzb2JqLCBpbWFnZSwgcHRfc2l6ZSA9IHB0X3NpemUpLCBwbG90X2Z1bmN0aW9uYWxfZ2VuZXMoc29iaiwgaW1hZ2UsIGdyb3VwID0gJ2ltYWdlJyksIHBsb3RfY2x1bXBfY2VsbHR5cGUoc29iaiwgaW1hZ2UsIHB0X3NpemUgPSBwdF9zaXplKSksIHBsb3RzKQogIHBsb3RzID0gYXBwZW5kKGxpc3QocGxvdF9jbHVtcF9jbHVzdChzb2JqLCBpbWFnZSwgcHRfc2l6ZSA9IHB0X3NpemUpLCBwbG90X2Z1bmN0aW9uYWxfZ2VuZXMoc29iaiwgaW1hZ2UsIGdyb3VwID0gJ2ltYWdlJykpLCBwbG90cykKICBhbGxfcGxvdHMgPSBwbG90X2dyaWQocGxvdGxpc3QgPSBwbG90cywgbGFiZWxfc2l6ZSA9IDEsIG5yb3cgPSA5KQogIGFsbF9wbG90cwp9CgoKI3BkZigiL2hvbWUvYXVub3kvc3QvYXJjX3Byb2ZpbGluZy9zdF9hbmFseXNpcy9yZXN1bHRzL3Bsb3RzLzIwMjIwODE3XzEvVENfY2x1bXBfcHJvZmlsZXMucGRmIiwgb25lZmlsZSA9IFRSVUUpCmZvcihpbWFnZSBpbiB1bmlxdWUoZGZfNDA4JElNQUdFLk5BTUUpKXsKICBhcnJhbmdlZCA9IHBsb3RfY2x1c3Rlcl9wcm9maWxlKGp5XzQwOCwgaW1hZ2UsIHB0X3NpemUgPSA1KQogIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMChpbWFnZSwgJ180MDhfY2x1bXBfcHJvZmlsZXMucGRmJyksIHBhdGggPSBmaWxlLnBhdGgob3V0cHV0X2Rpcl9wbG90LCAnMjAyMjA4MjJfMScpLCBhcnJhbmdlZCwgCiAgICAgICAgIGhlaWdodCA9IDE4LCB3aWR0aCA9IDEyKQp9CiNkZXYub2ZmKCkKCiMjIEZpbmFsIGxpbmVzCiNtYXJyYW5nZUdyb2IocGxvdHMsIG5yb3c9OCwgbmNvbD00KQojbWwgPC0gbWFycmFuZ2VHcm9iKHBsb3RzLCBucm93PTIsIG5jb2w9MikKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxOCwgZmlnLndpZHRoID0gMTh9CnBsb3RzID0gbGlzdCgpCnBsb3RzIDwtIGxhcHBseSgxOmxlbmd0aCh1bmlxdWUoZGZfNDA4JElNQUdFLk5BTUUpKSwgZnVuY3Rpb24oaSl7CiAgcGxvdF9jbHVtcF9leHByKGp5XzQwOCwgdW5pcXVlKGRmXzQwOCRJTUFHRS5OQU1FKVtpXSwgJ0NPVVBURjInLCBwdF9zaXplID0gOCwgSU1BR0VfU1BFQ0lGSUMgPSBUUlVFKQp9KQphbGxfcGxvdHMgPSBwbG90X2dyaWQocGxvdGxpc3QgPSBwbG90cywgbGFiZWxfc2l6ZSA9IDEsIG5yb3cgPSA2KQphbGxfcGxvdHMKZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKCc0MDhfQ09VUFRGMl9jbHVtcF9wcm9maWxlcy5wZGYnKSwgcGF0aCA9IGZpbGUucGF0aChvdXRwdXRfZGlyX3Bsb3QsICcyMDIyMDgyM18xJyksIGFsbF9wbG90cywgCiAgICAgICAgIGhlaWdodCA9IDE4LCB3aWR0aCA9IDE4KQpgYGAKYGBge3J9CnN1bShGZXRjaERhdGEoanlfYWxsLCAnR0FEMScpICE9IDApIC8gc3VtKEZldGNoRGF0YShqeV9hbGwsICdHQUQxJykgPT0gMCkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gNC41fQpEaW1QbG90KGp5X2FsbCkgKyBOb0F4ZXMoKQpgYGAKCgpgYGB7cn0KRmVhdHVyZVBsb3QoanlfYWxsLCBjZWxscyA9IHdoaWNoKElkZW50cyhqeV9hbGwpID09ICdWSVArR0FEMSsnKSwgYygnQ09VUFRGMicsICdTUDgnLCAnUFJPWDEnLCAnRExYMicpLCBjb2xzID0gYygnbGlnaHRncmV5JywgaHVlX3BhbCgpKDkpWzVdKSkKYGBgCmBgYHtyfQpGZWF0dXJlUGxvdChqeV9hbGwsIGNlbGxzID0gd2hpY2goSWRlbnRzKGp5X2FsbCkgPT0gJ1ZJUCtHQUQxKycpLCBjKCdWSVAnLCAnTEhYNicsICdOS1gyLjEnLCAnR0FEMScpLCBjb2xzID0gYygnbGlnaHRncmV5JywgaHVlX3BhbCgpKDkpWzVdKSkKYGBgCgoKYGBge3J9CkZlYXR1cmVQbG90KGp5X2FsbCwgIGMoJ0NPVVBURjInLCAnU1A4JywgJ1BST1gxJywgJ0RMWDInKSwgY29scyA9IGMoJ2xpZ2h0Z3JleScsIGh1ZV9wYWwoKSg5KVs1XSkpCmBgYAoKYGBge3J9CkZlYXR1cmVQbG90KGp5X2FsbCwgYygnTktYMi4xJywgJ0xIWDYnLCAnTUFGMScsICdHQUQxJyksIGNvbHMgPSBjKCdsaWdodGdyZXknLCBodWVfcGFsKCkoOSlbOV0pKQpgYGAKCmBgYHtyfQpGZWF0dXJlUGxvdChqeV9hbGwsIGMoJ1RTSFoxJywgJ0dTWDInLCAnR0FEMScpLCBjb2xzID0gYygnbGlnaHRncmV5JywgaHVlX3BhbCgpKDkpWzFdKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gNH0KRmVhdHVyZVBsb3QoanlfYWxsLCAnRENYJywgY29scyA9IGMoJ2xpZ2h0Z3JleScsICdncmVlbicpKSArIE5vQXhlcygpICsgTm9MZWdlbmQoKQpgYGAKYGBge3IgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDE1fQpGZWF0dXJlUGxvdChqeV9hbGwsICBjKCdMSFg2JywgJ1NTVCcpLCBjb2xzID0gYygnbGlnaHRncmV5JywgaHVlX3BhbCgpKDcpWzZdKSwgc3BsaXQuYnkgPSAnYXJlYScsIGJ5LmNvbCA9IFRSVUUpCmBgYApgYGB7ciBmaWcuaGVpZ2h0ID0gMiwgZmlnLndpZGd0aCA9IDN9CmdlbmVzID0gYygnTEhYNicsICdTU1QnKQpwbG90cyA8LSBsYXBwbHkoMTpsZW5ndGgoZ2VuZXMpLCBmdW5jdGlvbihpKXsKICAgIHBsb3RfZmVhdHVyZXNfdW1hcChqeV9hbGwsIGdlbmVzW2ldLCBwdC5zaXplID0gMC44LCBhbHBoYSA9IDEsIGNvbG9yID0gaHVlX3BhbCgpKDcpWzZdKQogIH0pCnVtYXBzID0gcGxvdF9ncmlkKHBsb3RsaXN0ID0gcGxvdHMsIGxhYmVsX3NpemUgPSAxMCwgbnJvdyA9IDEpCnVtYXBzCmBgYApgYGB7ciBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTJ9CkRpbVBsb3QoanlfYWxsLCBzcGxpdC5ieSA9ICdhcmVhJywgbmNvbCA9IDMpCmBgYAoKYGBge3IgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAyfQpGZWF0dXJlUGxvdChqeV9hbGwsIGMoJ1RCUjEnLCAnQ09VUFRGMicpLCBjb2xzID0gYygnI0YxOEYwMScsICcjMDQ4QkE4JyksIGJsZW5kPSBUUlVFKSAKYGBgCmBgYHtyIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gM30KRmVhdHVyZVBsb3QoanlfYWxsLCBjKCdWSVAnLCAnU1NUJyksIGNvbHMgPSBjKCAnI0YxOEYwMScsICcjMDQ4QkE4JyksIGJsZW5kPSBUUlVFKSAKYGBgCgpgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDN9CkZlYXR1cmVQbG90KGp5X2FsbCwgYygnVEJSMScsICdDT1VQVEYyJyksIGNvbHMgPSBjKCAnI0YxOEYwMScsICcjMDQ4QkE4JyksIGJsZW5kPSBUUlVFKSAKYGBgCgpgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CkRpbVBsb3QoanlfYWxsLCBzcGxpdC5ieSA9ICdhcmVhJywgbmNvbCA9IDMpIApgYGAKYGBge3IgZmlnLndpZHRoID0gMTgsIGZpZy5oZWlnaHQgPSA1fQpGZWF0dXJlUGxvdChqeV9hbGwsIGZlYXR1cmVzID0gYygnR0FEMScsICdETFgyJyksIHNwbGl0LmJ5ID0gJ2FyZWEnLCBieS5jb2wgPSBUUlVFKSAKYGBgCgpgYGB7cn0KanlfYWxsCmBgYAoKIyMgVHJlZQpgYGB7cn0KYnJlYWtwb2ludHMgPSAxOjQwLzIwCmkgPSAxCnJlc19zdHIgPSAnY2x1c3RfdHJlZV9yZXMuJwpub3JtX3N0ciA9ICdSTkFfc25uX3Jlcy4nCmZvciAoYnJlYWtwb2ludCBpbiBicmVha3BvaW50cyl7CiAganlfYWxsIDwtIEZpbmRDbHVzdGVycyhqeV9hbGwsIHJlc29sdXRpb24gPSBicmVha3BvaW50LCB2ZXJib3NlID0gRkFMU0UpCiAganlfYWxsW1twYXN0ZTAocmVzX3N0ciwgYnJlYWtwb2ludCldXSA9IGp5X2FsbFtbcGFzdGUwKG5vcm1fc3RyLCBicmVha3BvaW50KV1dCn0KYGBgCgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTUsIGZpZy53aWR0aCA9IDEwfQpsaWJyYXJ5KGNsdXN0cmVlKQpjbHVzdHJlZShqeV9hbGwsIHByZWZpeCA9IHJlc19zdHIpCmBgYAoKYGBge3IgZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA0fQpEb3RQbG90KGp5X2FsbCwgZmVhdHVyZXMgPSBhbGwuZ2VuZXMpICsgUm90YXRlZEF4aXMoKQpgYGAKYGBge3IgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDJ9CkRvdFBsb3QoanlfYWxsLCBmZWF0dXJlcyA9IGFsbC5nZW5lcywgZ3JvdXAuYnkgPSAnYnJvYWRfYXJlYXMnKSArIFJvdGF0ZWRBeGlzKCkKYGBgCgpgYGB7cn0KRGltUGxvdChqeV9hbGwsIHNwbGl0LmJ5ID0gJ2Jyb2FkX2FyZWFzJywgbmNvbCA9IDIpCmBgYAoKYGBge3IgZmlnLndpZHRoID0gMS41LCBmaWcuaGVpZ2h0ID0xfQpEaW1QbG90KGp5X2FsbCwgZ3JvdXAuYnkgPSAnYnJvYWRfYXJlYXMnKSArIE5vQXhlcygpCmBgYAoKYGBge3J9ClhZXzE2NCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBwaXhlbF8xLCB5ID0gcGl4ZWxfMikpICsgZ2VvbV9wb2ludCgpICsgeApgYGAKCg==